0
0

GO言語ORMによるデータベース操作(jinzhu)によるDBスコープエラー

Last updated at Posted at 2024-09-16

※初心者のため、間違いや何かご存じの方がいましたら、教えていただけると助かります。

概要

以下のサイトに従って、ginによる認証を実装をしていたところ、
「サインアップ」エンドポイント ユーザー情報登録の項目で、POSTしたら、以下の 500 Internal Server Errorが発生した。

main.goは以下

package main

import (
	"jwt-gin/controllers"
	"jwt-gin/models"

	"github.com/gin-gonic/gin"
)

func main(){
	models.ConnectDataBase()
	
	router := gin.Default()

	public := router.Group("/api")

	public.POST("/register", controllers.Register)

	router.Run(":8080")
}

setup.goは以下

package models

import(
	"fmt"
	"log"
	"os"

	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	"github.com/joho/godotenv"
)

var DB *gorm.DB

func ConnectDataBase(){
	err := godotenv.Load()

	if err != nil {
		log.Fatalf("Error loading .env file")
	}

	driver := os.Getenv("DB_DRIVER")
	dbUser := os.Getenv("DB_USER")
	dbPass := os.Getenv("DB_PASS")
	dbName := os.Getenv("DB_NAME")
	dbHost := os.Getenv("DB_HOST")
	dbPort := os.Getenv("DB_PORT")

	dbURI := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Tokyo", dbHost, dbUser, dbPass, dbName, dbPort)

	fmt.Println(dbURI)
	fmt.Println(driver)

	DB, err := gorm.Open(driver, dbURI)

	if err != nil{
		log.Fatal("Could not connect to the database:", err)
	}

	DB.AutoMigrate(&User{})

}

auth.go(ユーザー登録するRegister関数)は以下

package controllers

import (
	"fmt"
	"net/http"
	"jwt-gin/models"
	"github.com/gin-gonic/gin"
)

type RegisterInput struct{
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

func Register(c *gin.Context){
	var input RegisterInput

	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	fmt.Printf("%+v\n",input)  //inputの確認

	user := models.User{
		Username: input.Username, Password: input.Password,
	}

	fmt.Printf("%+v\n", user)  //userの確認 ← ここは問題なし。

	user, err := user.Save()   //← これが実行されないエラー箇所
	
	fmt.Printf("%+v\n", user)  //userの確認 ← これが実行されない。
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{
		"data": user.PrepareOutput(),
	})
}

user.go(user.Save()の中身)は以下

package models

import (
	"strings"

	"github.com/jinzhu/gorm"
	"golang.org/x/crypto/bcrypt"
)

type User struct {
    gorm.Model
    Username string `gorm:"size:255;not null;unique" json:"username"`
    Password string `gorm:"size:255;not null;" json:"password"`
}

func (u User) Save() (User, error) {
    err := DB.Create(&u).Error
    if err != nil {
        return User{}, err
    }
    return u, nil
}

func (u *User) BeforeSave() error {
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
    if err != nil {
        return err
    }

    u.Password = string(hashedPassword)

    u.Username = strings.ToLower(u.Username)

    return nil
}

func (u User) PrepareOutput() User {
    u.Password = ""
    return u
}

環境

windows11
docker-desktop
go 1.22

エラーメッセージ

//main.goで8080番でサーバー立ち上げ
# go run main.go

[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] POST   /api/register             --> jwt-gin/controllers.Register (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.   
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Listening and serving HTTP on :8080

/*
ここで、http://localhost:8080/api/registerへPOSTリクエスト。bodyには以下のJSON形式
{"username":"admin", "password":"pass"}
*/

2024/09/16 16:07:30 [Recovery] 2024/09/16 - 16:07:30 panic recovered:
POST /api/register HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: close
Content-Length: 38
Content-Type: application/json
User-Agent: got (https://github.com/sindresorhus/got)


runtime error: invalid memory address or nil pointer dereference
/usr/local/go/src/runtime/panic.go:261 (0x4558b7)
        panicmem: panic(memoryError)
/usr/local/go/src/runtime/signal_unix.go:881 (0x455885)
        sigpanic: panicmem()
/go/pkg/mod/github.com/jinzhu/gorm@v1.9.16/main.go:853 (0x74d777)
        (*DB).clone: dialect:           newDialect(s.dialect.GetName(), s.db),
/go/pkg/mod/github.com/jinzhu/gorm@v1.9.16/main.go:204 (0x746e25)
        (*DB).NewScope: dbClone := s.clone()
/go/pkg/mod/github.com/jinzhu/gorm@v1.9.16/main.go:482 (0x74a8c4)
        (*DB).Create: scope := s.NewScope(value)
/app/jwt-gin/models/user.go:17 (0x9719eb)
        User.Save: err := DB.Create(&u).Error
/app/jwt-gin/controllers/auth.go:33 (0x97194e)
        Register: user, err := user.Save()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x968059)
        (*Context).Next: c.handlers[c.index](c)
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/recovery.go:102 (0x968047)
        CustomRecoveryWithWriter.func1: c.Next()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x967184)
        (*Context).Next: c.handlers[c.index](c)
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/logger.go:249 (0x96716b)
        LoggerWithConfig.func1: c.Next()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/context.go:185 (0x966571)
        (*Context).Next: c.handlers[c.index](c)
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:633 (0x965fe0)
        (*Engine).handleHTTPRequest: c.Next()
/go/pkg/mod/github.com/gin-gonic/gin@v1.10.0/gin.go:589 (0x965b11)
        (*Engine).ServeHTTP: engine.handleHTTPRequest(c)
/usr/local/go/src/net/http/server.go:3142 (0x6cb6ed)
        serverHandler.ServeHTTP: handler.ServeHTTP(rw, req)
/usr/local/go/src/net/http/server.go:2044 (0x6c6aa7)
        (*conn).serve: serverHandler{c.server}.ServeHTTP(w, w.req)
/usr/local/go/src/runtime/asm_amd64.s:1695 (0x472e00)
        goexit: BYTE    $0x90   // NOP

[GIN] 2024/09/16 - 16:07:30 | 500 |   85.893591ms |      172.20.0.1 | POST     "/api/register"      

解決策

ユーザー登録Registerのuser.Save()時に、DBへの接続ができないエラーらしい。
しかし、DBには以下のsetup.goからUserテーブルを作ることはできていた。
結論、原因としては、setup.goの以下で引っかかっていた。

DB, err := gorm.Open(driver, dbURI)

⇒ 正しくは以下

DB, err = gorm.Open(driver, dbURI)

DBを:=で書いたことで代入ではなく宣言扱いになってしまい、他の関数からDBにアクセスできなくなったこと(スコープミス)が原因と思われた。
以下は、bingAIに聞いた回答。

`DB, err := gorm.Open(driver, dbURI)` と `DB, err = gorm.Open(driver, dbURI)` の違いは、変数のスコープにあります。

1. **`DB, err := gorm.Open(driver, dbURI)`**:
   - これは新しい変数 `DB` と `err` を関数内で宣言しています。この場合、関数内で宣言された `DB` 変数は、パッケージレベルで宣言された `DB` 変数とは異なるため、パッケージレベルの `DB` 変数は更新されません。
   - その結果、関数外で `DB` を参照すると `nil` のままになり、エラーが発生します。

2. **`DB, err = gorm.Open(driver, dbURI)`**:
   - これは既にパッケージレベルで宣言されている `DB` 変数と `err` 変数に値を代入しています。この場合、パッケージレベルの `DB` 変数が更新されるため、関数外でも正しく初期化された `DB` を参照できます。

したがって、`DB, err = gorm.Open(driver, dbURI)` を使用することで、パッケージレベルの `DB` 変数が正しく初期化され、他の関数からもアクセスできるようになります。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0