Как получить доступ к Gorm в контроллере Revel?

позвольте мне начать с того, что это моя первая пара дней игры в Go.

Я пытаюсь использовать структуру Revel с Gorm следующим образом:

app/controllers/gorm.go

package controllers

import (
    "fmt"
    "go-testapp/app/models"

    _ "github.com/go-sql-driver/mysql"
    "github.com/jinzhu/gorm"
    "github.com/revel/revel"
)

var DB gorm.DB

func InitDB() {
    var err error
    DB, err = gorm.Open("mysql", "root:@/go-testapp?charset=utf8&parseTime=True")
    if err != nil {
        panic(err)
    }
    DB.LogMode(true)
    DB.AutoMigrate(models.User{})
}

type GormController struct {
    *revel.Controller
    DB *gorm.DB
}

app/controller/app.go

package controllers

import (
    "fmt"
    "go-bingo/app/models"

    _ "github.com/go-sql-driver/mysql"
    "github.com/revel/revel"
)

type App struct {
    GormController
}

func (c App) Index() revel.Result {
    user := models.User{Name: "Jinzhu", Age: 18}

    fmt.Println(c.DB)
    c.DB.NewRecord(user)

    c.DB.Create(&user)

    return c.RenderJson(user)
}

После запуска получается:

runtime error: invalid memory address or nil pointer dereference в строке 19 c.DB.NewRecord(user)

Он успешно создает таблицы данных с автоматической миграцией, но я понятия не имею, как мне использовать Gorm в моем контроллере.

Любые намеки в правильном направлении?


person Martijn19    schedule 05.08.2014    source источник
comment
вы можете настроить GORM в инициализации приложения, а затем использовать app.DB, чтобы ваше соединение с базой данных можно было использовать для разных запросов? revel.github.io/manual/database.html   -  person robert king    schedule 08.02.2020


Ответы (4)


Важная заметка

это просто замена GORP из исходный пример Revel . И это связано с некоторыми подводными камнями происхождения. Этот ответ можно использовать в качестве замены исходного. Но это не избавляет от подводных камней.

Пожалуйста, ознакомьтесь с комментариями к этому ответу и @MaxGabriel от ответ, который устраняет подводные камни.

Я бы рекомендовал использовать решение @MaxGabriel для защиты вашего приложения от некоторых видов медленных DDoS-атак. И снизить (в некоторых случаях) давление на БД.

Оригинальный ответ

@rauyran права, вы должны вызвать InitDB внутри функции init (в пакете controllers).

Полный пример здесь (слишком много):

Дерево

/app
    /controllers
      app.go
      gorm.go
      init.go
    /models
      user.go
[...]

user.go

// models/user.go
package models

import  "time" // if you need/want

type User struct {          // example user fields
    Id                    int64
    Name                  string
    EncryptedPassword     []byte
    Password              string      `sql:"-"`
    CreatedAt             time.Time
    UpdatedAt             time.Time
    DeletedAt             time.Time     // for soft delete
}

gorm.go

//controllers/gorm.go
package controllers

import (
    "github.com/jinzhu/gorm"
    _ "github.com/lib/pq" // my example for postgres
    // short name for revel
    r "github.com/revel/revel"
    // YOUR APP NAME
    "yourappname/app/models"
    "database/sql"
)

// type: revel controller with `*gorm.DB`
// c.Txn will keep `Gdb *gorm.DB`
type GormController struct {
    *r.Controller
    Txn *gorm.DB
}

// it can be used for jobs
var Gdb *gorm.DB

// init db
func InitDB() {
    var err error
    // open db
    Gdb, err = gorm.Open("postgres", "user=uname dbname=udbname sslmode=disable password=supersecret")
    if err != nil {
        r.ERROR.Println("FATAL", err)
        panic( err )
    }
    Gdb.AutoMigrate(&models.User{})
    // unique index if need
    //Gdb.Model(&models.User{}).AddUniqueIndex("idx_user_name", "name")
}


// transactions

// This method fills the c.Txn before each transaction
func (c *GormController) Begin() r.Result {
    txn := Gdb.Begin()
    if txn.Error != nil {
        panic(txn.Error)
    }
    c.Txn = txn
    return nil
}

// This method clears the c.Txn after each transaction
func (c *GormController) Commit() r.Result {
    if c.Txn == nil {
        return nil
    }
    c.Txn.Commit()
    if err := c.Txn.Error; err != nil && err != sql.ErrTxDone {
        panic(err)
    }
    c.Txn = nil
    return nil
}

// This method clears the c.Txn after each transaction, too
func (c *GormController) Rollback() r.Result {
    if c.Txn == nil {
        return nil
    }
    c.Txn.Rollback()
    if err := c.Txn.Error; err != nil && err != sql.ErrTxDone {
        panic(err)
    }
    c.Txn = nil
    return nil
}

app.go

package controllers

import(
    "github.com/revel/revel"
    "yourappname/app/models"
)

type App struct {
    GormController
}

func (c App) Index() revel.Result {
    user := models.User{Name: "Jinzhup"}
    c.Txn.NewRecord(user)
    c.Txn.Create(&user)
    return c.RenderJSON(user)
}

init.go

package controllers
import "github.com/revel/revel"

func init() {
    revel.OnAppStart(InitDB) // invoke InitDB function before
    revel.InterceptMethod((*GormController).Begin, revel.BEFORE)
    revel.InterceptMethod((*GormController).Commit, revel.AFTER)
    revel.InterceptMethod((*GormController).Rollback, revel.FINALLY)
}

Как видите, это как Revel's Booking, модифицированный для GORM.

У меня работает нормально. Результат:

{
  "Id": 5,
  "Name": "Jinzhup",
  "EncryptedPassword": null,
  "Password": "",
  "CreatedAt": "2014-09-22T17:55:14.828661062+04:00",
  "UpdatedAt": "2014-09-22T17:55:14.828661062+04:00",
  "DeletedAt": "0001-01-01T00:00:00Z"
}
person Ivan Black    schedule 22.09.2014
comment
Если я правильно понимаю, этот код создает транзакцию в начале каждого HTTP-запроса и закрывает ее в конце. Это может привести к потенциально длительным транзакциям, если ваш HTTP-сервер работает медленно (скажем, он заблокирован при внешнем HTTP-запросе или другой базе данных или в любом другом случае). Это использует больше ресурсов в вашей базе данных, и эти транзакции могут блокировать строки и вызывать взаимоблокировки. Почему бы просто не использовать var Gdb *gorm.DB напрямую? - person MaxGabriel; 20.10.2017
comment
@MaxGabriel, не стесняйтесь добавить еще один ответ, и я добавлю примечание, чтобы указать на ваш ответ. Это полная замена оригиналу со всеми его подводными камнями. Ничего больше. - person Ivan Black; 27.10.2017
comment
@MaxGabriel, да, я добавил примечание. - person Ivan Black; 01.11.2017

Этот ответ получен из ответа @IvanBlack по его предложению. Его версия является прямым переводом примера кода Revel, но мы выявили некоторые проблемы с исходным кодом, которые этот ответ исправляет. Основные изменения, которые он вносит:

  1. Весь HTTP-запрос больше не обертывается транзакцией базы данных. Обертка всего HTTP-запроса держит транзакцию открытой гораздо дольше, чем необходимо, что может вызвать ряд проблем:

    • Your transactions will hold locks on the database, and many transactions holding locks could lead to deadlocks
    • Используется больше ресурсов базы данных
    • Эти проблемы усугубляются, если ваши HTTP-запросы в основном не ограничиваются доступом к базе данных SQL. Например. если ваш HTTP-запрос блокируется на 30 секунд при отправке внешнего HTTP-запроса или доступе к другой базе данных, замедление работы этих служб может повлиять на вашу базу данных SQL.
  2. Поскольку транзакция больше не проверяется автоматически на наличие ошибок в конце HTTP-запроса, ошибки проверяются, как только выполняется вставка в базу данных. Это также более правильно: представьте, что после вставки структуры User в базу данных вы затем сохранили User.Id в другой базе данных, такой как Redis. Это было бы хорошо, если бы вставка в базу данных работала, но если бы она не удалась и вы не сразу проверили ошибку, вы бы вставили значение int64 по умолчанию, равное 0, в Redis (до последующего отката только транзакции SQL).

Дерево

/app
    /controllers
      app.go
      gorm.go
      init.go
    /models
      user.go
[...]

user.go

// models/user.go
package models

import  "time" // if you need/want

type User struct {          // example user fields
    Id                    int64
    Name                  string
    EncryptedPassword     []byte
    Password              string      `sql:"-"`
    CreatedAt             time.Time
    UpdatedAt             time.Time
    DeletedAt             time.Time     // for soft delete
}

gorm.go

//controllers/gorm.go
package controllers

import (
    "github.com/jinzhu/gorm"
    _ "github.com/lib/pq" // my example for postgres
    // short name for revel
    r "github.com/revel/revel"
)

// type: revel controller with `*gorm.DB`
type GormController struct {
    *r.Controller
    DB *gorm.DB
}

// it can be used for jobs
var Gdb *gorm.DB

// init db
func InitDB() {
    var err error
    // open db
    Gdb, err = gorm.Open("postgres", "user=USERNAME dbname=DBNAME sslmode=disable")
    Gdb.LogMode(true) // Print SQL statements
    if err != nil {
        r.ERROR.Println("FATAL", err)
        panic(err)
    }
}

func (c *GormController) SetDB() r.Result {
    c.DB = Gdb
    return nil
}

init.go

package controllers
import "github.com/revel/revel"

func init() {
    revel.OnAppStart(InitDB) // invoke InitDB function before
    revel.InterceptMethod((*GormController).SetDB, revel.BEFORE)
}

app.go

package controllers

import(
    "github.com/revel/revel"
    "yourappname/app/models"
)

type App struct {
    GormController
}

func (c App) Index() revel.Result {
    user := models.User{Name: "Jinzhup"} // Note: In practice you should initialize all struct fields
    if err := c.DB.Create(&user).Error; err != nil {
        panic(err)
    }
    return c.RenderJSON(user)
}

Результат:

{
  "Id": 5,
  "Name": "Jinzhup",
  "EncryptedPassword": null,
  "Password": "",
  "CreatedAt": "2014-09-22T17:55:14.828661062+04:00",
  "UpdatedAt": "2014-09-22T17:55:14.828661062+04:00",
  "DeletedAt": "0001-01-01T00:00:00Z"
}
person MaxGabriel    schedule 30.10.2017

Ваша ошибка вызвана тем, что вы не инициализировали переменную базы данных c.DB, она по-прежнему равна нулю.

В вашем файле controllers/init.go убедитесь, что вы вызываете revel.OnAppStart(InitDB). Это должно выглядеть примерно так:

package controllers

import "github.com/revel/revel"

func init() {
    revel.OnAppStart(InitDB)
    // maybe some other init things
}
person Ian Davis    schedule 05.08.2014
comment
Мой controllers/init.go выглядит точно так же, и мой InitDB внутри gorm.go вызывается правильно. Но, к сожалению, я понятия не имею, как передать Gorm на контроллер моего приложения. Вы правы, c.DB в моем console.log равно ‹nil› - person Martijn19; 05.08.2014
comment
Вы также устанавливаете методы перехвата, как показано здесь? github.com/revel/revel/blob/ мастер/образцы/бронирование/приложение/ - person Ian Davis; 05.08.2014
comment
Нет, потому что у меня нет (*GorpController).Begin и т. д., я думаю, это для Горпа. Я пытаюсь использовать Горм. - person Martijn19; 05.08.2014
comment
Вам необходимо реализовать эквивалентные методы Begin в вашем GormController, как это делает пример GorpController. - person Ian Davis; 05.08.2014
comment
Вы случайно не знаете, почему я должен это делать? Почему я не могу просто получить доступ к методу создания gorm внутри другого контроллера? - person Martijn19; 05.08.2014

ИЛИ вам нужно передать указатель на AutoMigrate?

 DB.AutoMigrate(&models.User{})
person DharmaDog    schedule 18.10.2017