본문으로 바로가기

[바미] Go - PostgreDB

category 프로그래밍(Web)/Golang 2020. 12. 18. 12:42
728x90
반응형
728x170

 

지금까지 Todos를 Heroku에 배포를 했습니다.

heroku에서는 dyno라는 컨테이너를 쓰는데, 이것이 statelss라 fileDB를 사용할 수 없게 되어 실제 DB를 사용해야 하는데

heroku가 클라우드 서비스이기 때문에 PostgreDB라는 DB서비스를 10000레코드까지만 무료로 제공하고 있습니다.

 

우선 커맨드 창을 띄운 뒤에 Heroku 로그인을 해줍니다. 그 후 해당 명령어를 입력합니다.

heroku addons:create heroku-postgresql:hobby-dev

이렇게 하면 현재 앱에 postgresqlDB가 추가가 되고, 티어는 hobby-dev인 무료티어로 추가가 됩니다.

 

Created postgresql-round-47123 as DATABASE_URL

라는 의미는 DATABASE_URL라는 이름으로 환경변수가 추가되었다는 의미입니다.

 

그래서 heroku config를 사용하여 환경변수를 조회하면 DATABASE_URL라는 이름으로 환경변수가 추가되었다는 것을 확인 할 수 있습니다.

이것을 이용해서 PostgreSQL에 접속할 수 있습니다.

 

Go에서 사용하기 위해서 패키지를 받아주어야 한다. 다음과 같이 입력 해줍니다.

go get github.com/lib/pq

pq가 postgreSQL을 의미 합니다.

 

그리고 이전에 만들었던 model/sqliteHandler.go로 와서 복사 붙여넣기해서 pqHandler.go로 이름만 바꾸어 줍니다.

 

그 후 코드를 다음과 같이 수정해 줍니다.

  package model

  import (
    "database/sql"
    "time"

    _ "github.com/mattn/go-sqlite3"
  )

  type pqHandler struct {
    db *sql.DB
  }

  func (s *pqHandler) GetTodos() []*Todo {
    todos := []*Todo{}
    rows, err := s.db.Query("SELECT id, name, completed, createdAt FROM todos")
    if err != nil {
      panic(err)
    }
    defer rows.Close()
    for rows.Next() {
      var todo Todo
      rows.Scan(&todo.ID, &todo.Name, &todo.Completed, &todo.CreatedAt)
      todos = append(todos, &todo)
    }
    return todos
  }

  func (s *pqHandler) AddTodo(name string) *Todo {
    stmt, err := s.db.Prepare("INSERT INTO todos (name, completed, createdAt) VALUES (?, ?, datetime('now'))")
    if err != nil {
      panic(err)
    }
    rst, err := stmt.Exec(name, false)
    if err != nil {
      panic(err)
    }
    id, _ := rst.LastInsertId()
    var todo Todo
    todo.ID = int(id)
    todo.Name = name
    todo.Completed = false
    todo.CreatedAt = time.Now()
    return &todo
  }

  func (s *pqHandler) RemoveTodo(id int) bool {
    stmt, err := s.db.Prepare("DELETE FROM todos WHERE id=?")
    if err != nil {
      panic(err)
    }
    rst, err := stmt.Exec(id)
    if err != nil {
      panic(err)
    }
    cnt, _ := rst.RowsAffected()
    return cnt > 0
  }

  func (s *pqHandler) CompleteTodo(id int, complete bool) bool {
    stmt, err := s.db.Prepare("UPDATE todos SET completed=? WHERE id=?")
    if err != nil {
      panic(err)
    }
    rst, err := stmt.Exec(complete, id)
    if err != nil {
      panic(err)
    }
    cnt, _ := rst.RowsAffected()
    return cnt > 0
  }

  func (s *pqHandler) Close() {
    s.db.Close()
  }

  func newPQHandler(filepath string) DBHandler {
    database, err := sql.Open("sqlite3", filepath)
    if err != nil {
      panic(err)
    }
    statement, _ := database.Prepare(
      `CREATE TABLE IF NOT EXISTS todos (
        id        INTEGER  PRIMARY KEY AUTOINCREMENT,
        name      TEXT,
        completed BOOLEAN,
        createdAt DATETIME
      )`)
    statement.Exec()
    return &sqliteHandler{db: database}
  }

이런식으로 기존 handler structpqHandler로 바꾼 뒤 이름만 바꾸어 줍니다.

그 후 model/model.go로 넘어와서 newPQHandler가 생성되도록 수정해 줍니다.

func NewDBHandler(dbConn string) DBHandler {
    //handler = newMemoryHandler()
    return newPQHandler(dbConn)
}

그리고 기존에는 filePath였는데 지금은 DB정보가 와야 하기 때문에 dbConn으로 변경해 줍니다.

 

마찬가지로 model/sqliteHandler.go도 수정해 줍니다.

import (
    "database/sql"
    "time"

    _ "github.com/lib/pq"
)

func newSqliteHandler(dbConn string) DBHandler {
    database, err := sql.Open("postgres", dbConn)
    if err != nil {
      panic(err)
    }

    statement, err := database.Prepare(
      `CREATE TABLE IF NOT EXISTS todos (
        id        INTEGER  PRIMARY KEY AUTOINCREMENT,
        name      TEXT,
        completed BOOLEAN,
        createdAt DATETIME
      )`)

    if err != nil {
      panic(err)
    }

    _, err = statement.Exec()

    if err != nil {
      panic(err)
    }

    return &sqliteHandler{db: database}
  }

마찬가지로 여기에서도 dbConn으로 변경해주었고, error를 받아서 error가 날 때마다 확인하도록 수정했고,

import 부분에서 go-sqlite3을 pq로, newSqliteHandler부분에서 postgres로 수정해 주었습니다.

 

그 후 main.go에서 file을 넣었는데, dbConn을 넣어 줍니다.

func main() {
    port := os.Getenv("PORT")
    m := app.MakeHandler(os.Getenv("DATABASE_URL"))
    defer m.Close()

    log.Println("Started App")
    err := http.ListenAndServe(":"+port, m)
    if err != nil {
      panic(err)
    }
}

이렇게 하면 준비는 끝났습니다. 저장 후에, add, commit, push를 해줍시다!

그 후 잘 동작하는지 log를 확인합니다.

log창을 보면 panic: runtime error: invalid memory address or nil pointer dereference이라는 에러가 떴는데, model/sqliteHandler.go에서 DB테이블을 만들 때

func newPQHandler(filepath string) DBHandler {
    database, err := sql.Open("sqlite3", filepath)
    if err != nil {
      panic(err)
    }
    statement, _ := database.Prepare(
      `CREATE TABLE IF NOT EXISTS todos (
        id        INTEGER  PRIMARY KEY AUTOINCREMENT,
        name      TEXT,
        completed BOOLEAN,
        createdAt DATETIME
      )`)
    statement.Exec()
    return &sqliteHandler{db: database}
}

id INTEGER PRIMARY KEY AUTOINCREMENT, 이 부분에서 에러가 났네요.

각 DB마다 문법이 다른데, 이 부분을

  func newPQHandler(dbConn string) DBHandler {
    database, err := sql.Open("postgres", dbConn)
    if err != nil {
      panic(err)
    }
    statement, err := database.Prepare(
      `CREATE TABLE IF NOT EXISTS todos (
        id        SERIAL PRIMARY KEY,
        sessionId VARCHAR(256),
        name      TEXT,
        completed BOOLEAN,
        createdAt TIMESTAMP
      );`)

    if err != nil {
      panic(err)
    }

    _, err = statement.Exec()

    if err != nil {
      panic(err)
    }

    statement, err = database.Prepare(
      `CREATE INDEX IF NOT EXISTS sessionIdIndexOnTodos ON todos (
          sessionId ASC
      );`)

    if err != nil {
      panic(err)
    }

    _, err = statement.Exec()

    if err != nil {
      panic(err)
    }

    return &sqliteHandler{db: database}
  }

로 바꾸어주면 되며, 한 번에 여러개의 커맨드를 하나로 사용할 수 없기 때문에 나누어서 실행시켜주도록 수정해 주어야 하고,

STRING을 사용할 수 없기 때문에 VARCHAR(256)으로 수정해주고, DATETIME을 사용할 수 없어 같은 기능을 하는 TIMESTAMP로 변경 해줍니다. 이후 저장 후 다시 add, commit, push를 진행합니다.

 

그런데... 아래와 같은 문제가 발생했습니다. 뭐가 문제일까요...?

출처 : www.youtube.com/channel/UCZp_ftx6UB_32VfVmlS3o_A

728x90
반응형
그리드형

댓글을 달아 주세요