質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.87%

「Go言語」 PostgreSQLにデータを保存できません。

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 944

uk_63

score 23

はじめに

質問を見てくださりありがとう御座います。
現在、Go言語の練習として、クライアント側のフォームからJSONを送信、サーバー側(Go)で受け取って、データベース(PostgreSQL)で保存させようとしています。
JSONを受け取ってPostgreSQLに接続までは成功しているのですが、保存が出来ません。

下記のパニックが出てました。

http: panic serving 127.0.0.1:50371: runtime error: invalid memory address or nil pointer dereference

構造体

2つあります。Room と Post です。
Room has_many Post という関係性です。

ここでは、まずRoomだけを作成してみようということで実践していましたが、DBへの保存のところでエラーが出ています。

コード(質問用に整形しています)

// package, import 部分は省略しています。


// Room ・誰でも投稿できる ・has_many Post
type Room struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    CreatedAt time.Time `json:"created_at"`
    Posts     []Post    `json:"posts"`
}

var postgreSQL *sql.DB

func init() {
    postgreSQL, err := sql.Open("postgres", "user=user dbname=db password=pw sslmode=disable")
    if err != nil {
        panic(err)
    }
    defer postgreSQL.Close()

    err = postgreSQL.Ping()
    if err != nil {
        panic(err)
    }

// 接続成功する。
    fmt.Print("Successfully connected!\n")
}

// SQLRoomCreate [ Create a new room ]
func (room *Room) SQLRoomCreate() (err error) {
    fmt.Print("\nRoomCreate is Starting!!\n\n")
    statement := "insert into rooms (title, content) values ($1, $2) returning id, created_at"

// ここでエラーが発生している!!
    stmt, err := postgreSQL.Prepare(statement)
    if err != nil {
        return
    }
    defer stmt.Close()
    err = stmt.QueryRow(room.Title, room.Content).Scan(&room.ID, &room.CreatedAt)
    fmt.Print("RoomCreate is DONE")
    return
}

func roomCreate(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        fmt.Print("不正なメソッドです。\n")
        fmt.Printf("Method : \n%v\n", r.Method)
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    if r.Header.Get("Content-Type") != "application/json" {
        fmt.Print("JSONではありません。\n")
        fmt.Printf("Response Header : \n%v\n", r.Header.Get("Content-Type"))
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    //To allocate slice for request body
    length, err := strconv.Atoi(r.Header.Get("Content-Length"))
    if err != nil {
        fmt.Printf("Content-Length : \n%v\n", r.Header.Get("Content-Length"))
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // Read メソッドが []byte を読み込むことになっている
    body := make([]byte, length)
    _, err = r.Body.Read(body)
    if err != nil && err != io.EOF {
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // 受け取ったJSONを解析して構造体 Room にマッピング
    // []byteをlength 分だけパースする
    var room model.Room
    err = json.Unmarshal(body[:length], &room)
    if err != nil {
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    err = room.SQLRoomCreate()
    if err != nil {
        fmt.Printf("*\nERROR in room.go\n*\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    w.WriteHeader(http.StatusOK)
    http.Redirect(w, r, "/room", 302)
}

func main() {
    mux := http.NewServeMux()

    files := http.FileServer(http.Dir("assets"))
    mux.Handle("/static/", http.StripPrefix("/static/", files))

    mux.HandleFunc("/", homeIndex)
    mux.HandleFunc("/room/new", roomCreate)

    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: mux,
    }
    log.Fatal(server.ListenAndServe())
}

コード(回答を受けて編集ました)

指摘を受けた部分を試してみましたが、同じくエラーが出ています。

http: panic serving 127.0.0.1:59381: runtime error: invalid memory address or nil pointer dereference

ログを見たところ、SQLRoomCreateという関数のところでエラーがでていると思われます。誤っている箇所がないか確認していただきたいです。よろしくおねがいします!

// package, import 部分は省略しています。


// Room ・誰でも投稿できる ・has_many Post
type Room struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    CreatedAt time.Time `json:"created_at"`
    Posts     []Post    `json:"posts"`
}

var postgreSQL *sql.DB
// ここでDBへの接続情報入力
const (
    host     = "localhost"
    port     = 8080
    user     = "user"
    password = "pw"
    dbname   = "database"
)

func init() {
    // 変数に入れました。
    psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        host, port, user, password, dbname)
    // それを代入しました。
    postgreSQL, err := sql.Open("postgres", psqlInfo)
    // init 内では使っていなためにエラーが出る。それの回避でアンダースコア。
    var _ = postgreSQL
    if err != nil {
        panic(err)
    }

    // ColseしているためにDBに接続していないと予想。

    // defer postgreSQL.Close()

    // err = postgreSQL.Ping()
    // if err != nil {
    //     panic(err)
    // }

    fmt.Print("Successfully connected!\n")
}


// SQLRoomCreate [ Create a new room ]
func (room *Room) SQLRoomCreate() (err error) {
    fmt.Print("\nRoomCreate is Starting!!\n\n")
    statement := "insert into rooms (title, content) values ($1, $2) returning id, created_at"

///////////////////////////
// ここでエラーが発生している!!??
//////////////////////////

    stmt, err := postgreSQL.Prepare(statement)
    if err != nil {
        return
    }
    defer stmt.Close()
    err = stmt.QueryRow(room.Title, room.Content).Scan(&room.ID, &room.CreatedAt)
    fmt.Print("RoomCreate is DONE")
    return
}

func roomCreate(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
        fmt.Print("不正なメソッドです。\n")
        fmt.Printf("Method : \n%v\n", r.Method)
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    if r.Header.Get("Content-Type") != "application/json" {
        fmt.Print("JSONではありません。\n")
        fmt.Printf("Response Header : \n%v\n", r.Header.Get("Content-Type"))
        w.WriteHeader(http.StatusBadRequest)
        return
    }

    //To allocate slice for request body
    length, err := strconv.Atoi(r.Header.Get("Content-Length"))
    if err != nil {
        fmt.Printf("Content-Length : \n%v\n", r.Header.Get("Content-Length"))
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // Read メソッドが []byte を読み込むことになっている
    body := make([]byte, length)
    _, err = r.Body.Read(body)
    if err != nil && err != io.EOF {
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    // 受け取ったJSONを解析して構造体 Room にマッピング
    // []byteをlength 分だけパースする
    var room model.Room
    err = json.Unmarshal(body[:length], &room)
    if err != nil {
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }

    err = room.SQLRoomCreate()
    if err != nil {
        fmt.Printf("*\nERROR in room.go\n*\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)
        return
    }
    w.WriteHeader(http.StatusOK)
    http.Redirect(w, r, "/room", 302)
}

func main() {
    mux := http.NewServeMux()

    files := http.FileServer(http.Dir("assets"))
    mux.Handle("/static/", http.StripPrefix("/static/", files))

    mux.HandleFunc("/", homeIndex)
    mux.HandleFunc("/room/new", roomCreate)

    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: mux,
    }
    log.Fatal(server.ListenAndServe())
}

質問

①タイムスタンプはいつ押すべきなのかわからないため、created_atにデータを入れる処理を書いていません。いつその処理を書くべきですか? もしくはDB保存時に打刻されますか?

②プリペアドステートメントは間違っていないかチェックしていただきたいです。書籍を参考にしながらかいたので間違っているかもしれません。

どうかこのエラー解決に協力してください。お願いします!

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

ぱっと見た感じですが提示しているコードだけ見ると func initに問題があるのではないでしょうか?

とりあえずコードにコメントしておきます(間違ってたらすみません)

var postgreSQL *sql.DB

func init() {
    // := は宣言と代入をいっぺんに行うので上で宣言している以上ここで使うのは違う気がする
    postgreSQL, err := sql.Open("postgres", "user=user dbname=db password=pw sslmode=disable")
    if err != nil {
        panic(err)
    }
    // ここが問題
    // deferは全ての実行が終わると最後に呼び出されるのでinit関数の実行が終了した時点で
    // DBとの接続をクローズしてしまっている可能性大
    defer postgreSQL.Close()

    err = postgreSQL.Ping()
    if err != nil {
        panic(err)
    }

// 接続成功する。
    fmt.Print("Successfully connected!\n")
}

2/17 追記

必要な時にinit関数呼んであげてdaoをポインタで返せばいいかと思います
なるべく元の物に変更を加えないようにしたので正直イケてないですが恐らく動くかと

type Room struct {
    ID        int       `json:"id"`
    Title     string    `json:"title"`
    Content   string    `json:"content"`
    CreatedAt time.Time `json:"created_at"`
    Posts     []Post    `json:"posts"`
}

const (
    host     = "localhost"
    port     = 8080
    user     = "user"
    password = "pw"
    dbname   = "database"
)

func init() (*sql.DB, error) {
    psqlInfo := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
        host, port, user, password, dbname)

    postgreSQL, err := sql.Open("postgres", psqlInfo)

    if err != nil {
        return nil, err
    }

    fmt.Print("Successfully connected!\n")

    return &postgreSQL, nil
}

func (room *Room) SQLRoomCreate() (err error) {
    postgreSQL, err := init()

    if err != nil {
        panic(err)
    }

    defer postgreSQL.Close()

    err = postgreSQL.Ping()

    if err != nil {
        panic(err)
    }

    fmt.Print("\nRoomCreate is Starting!!\n\n")
    statement := "insert into rooms (title, content) values ($1, $2) returning id, created_at"

    stmt, err := postgreSQL.Prepare(statement)

    if err != nil {
        return
    }

    defer stmt.Close()
    err = stmt.QueryRow(room.Title, room.Content).Scan(&room.ID, &room.CreatedAt)
    fmt.Print("RoomCreate is DONE")

    return nil
}

func roomCreate(w http.ResponseWriter, r *http.Request) {
    if r.Method != "POST" {
            fmt.Print("不正なメソッドです。\n")
            fmt.Printf("Method : \n%v\n", r.Method)
            w.WriteHeader(http.StatusBadRequest)

            return
        }

    if r.Header.Get("Content-Type") != "application/json" {
            fmt.Print("JSONではありません。\n")
            fmt.Printf("Response Header : \n%v\n", r.Header.Get("Content-Type"))
            w.WriteHeader(http.StatusBadRequest)

            return
    }

    length, err := strconv.Atoi(r.Header.Get("Content-Length"))

    if err != nil {
        fmt.Printf("Content-Length : \n%v\n", r.Header.Get("Content-Length"))
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)

        return
    }

    body := make([]byte, length)
        _, err = r.Body.Read(body)

    if err != nil && err != io.EOF {
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)

        return
    }

    var room model.Room

    err = json.Unmarshal(body[:length], &room)

    if err != nil {
        fmt.Print("エラーが発生しました。\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)

        return
    }

    err = room.SQLRoomCreate()

    if err != nil {
        fmt.Printf("*\nERROR in room.go\n*\n")
        log.Fatal(err)
        w.WriteHeader(http.StatusInternalServerError)

        return
    }

    w.WriteHeader(http.StatusOK)
    http.Redirect(w, r, "/room", 302)
}

func main() {
    mux := http.NewServeMux()

    files := http.FileServer(http.Dir("assets"))
    mux.Handle("/static/", http.StripPrefix("/static/", files))

    mux.HandleFunc("/", homeIndex)
    mux.HandleFunc("/room/new", roomCreate)

    server := http.Server{
        Addr:    "127.0.0.1:8080",
        Handler: mux,
    }

    log.Fatal(server.ListenAndServe())
}

2/17追記 2

postgresの環境を作るのが面倒なので検証していませんがDBアクセスに必要な前準備の部分だけ作ってみました
これを元に必要な処理の追加などやってみてはいかがでしょうか?
ちなみにdbのポートはサーバーポートと同じではダメです。

// main.go

package main

import (
    "github.com/yourname/projectname/controller"
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()

    controller.NewRouter(mux)
    log.Fatal(http.ListenAndServe(":8080", mux))
}
// router.go

package controller

import (
    "net/http"
)

func NewRouter(mux *http.ServeMux) {
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(200)
        w.Write([]byte("Index"))
    })

    mux.HandleFunc("/room/new", NewRoom)
}
// room.go

package controller

import (
    _ "github.com/lib/pq"

    "database/sql"
    "net/http"
)

func NewRoom(w http.ResponseWriter, r *http.Request) {
    db, err := sql.Open("postgres", "host=127.0.0.1 port=5555 user=user password=pw dbname=database")

    defer db.Close()

    if err != nil {
        http.Error(w, err.Error(), 500)
    }

    // リクエストのパース(JSONから値を抜き出す)

    // データベースの操作(書き込みとか)
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/02/17 16:58

    回答ありがとうございます。
    > リクエストがDBアクセスを行う処理に対する物であればアクセスされたタイミングで接続して処理が終わったら切断する
    この処理の実装に取り組んでみます。
    返事遅れますが、都合が許す限りお付き合いください。
    よろしくおねがいします!

    キャンセル

  • 2019/02/27 00:38

    ベストアンサー遅れてすみません。
    質問にお付き合いしてくださり、また丁寧な回答をありがとうございました。

    キャンセル

  • 2019/03/01 01:41

    解決できたようでよかったです

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.87%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる