如何在Revel控制器中访问Gorm?

huangapple go评论79阅读模式
英文:

How to access Gorm in Revel Controller?

问题

让我从头开始说,这是我在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)

它成功地使用automigrate创建了数据表,但我不知道如何在控制器中使用Gorm。

对于正确的方向,有什么提示吗?

英文:

let me start by saying these are my first couple days of toying around in Go.

I'm trying to use the Revel framework with Gorm like this:

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)
}

After running it results in:

runtime error: invalid memory address or nil pointer dereference on line 19 c.DB.NewRecord(user)

It successfully creates the datatables with automigrate, but I have no idea how I should use Gorm in my controller.

Any hints in the right direction?

答案1

得分: 11

重要提示

这只是Revel原始示例GORP的替代品。它具有一些原始示例的缺点。这个答案可以作为原始答案的替代品。但它并没有解决这些缺点。

请查看此答案的评论和@MaxGabriel答案,它解决了这些缺点。

我建议使用@MaxGabriel的解决方案来保护您的应用程序免受某些类型的慢速DDoS攻击,并减少(在某些情况下)对数据库的压力。

原始答案

@rauyran的回答,你需要在init函数(位于controllers包中)中调用InitDB

完整示例如下(太长了):

目录结构

<!-- language: lang-none -->

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

user.go

<!-- language: java -->

// models/user.go
package models

import  &quot;time&quot; // 如果需要/想要的话

type User struct {          // 示例用户字段
    Id                    int64
    Name                  string
    EncryptedPassword     []byte
    Password              string      `sql:&quot;-&quot;`
    CreatedAt             time.Time
    UpdatedAt             time.Time
    DeletedAt             time.Time 	// 用于软删除
}

gorm.go

<!-- language: java -->

//controllers/gorm.go
package controllers

import (
	&quot;github.com/jinzhu/gorm&quot;
    _ &quot;github.com/lib/pq&quot; // 我的PostgreSQL示例
    // Revel的简称
    r &quot;github.com/revel/revel&quot;
    // 您的应用程序名称
    &quot;yourappname/app/models&quot;
    &quot;database/sql&quot;
)

// 类型:带有`*gorm.DB`的Revel控制器
// c.Txn将保持`Gdb *gorm.DB`
type GormController struct {
	*r.Controller
	Txn *gorm.DB
}

// 可用于作业
var Gdb *gorm.DB

// 初始化数据库
func InitDB() {
	var err error
	// 打开数据库
	Gdb, err = gorm.Open(&quot;postgres&quot;, &quot;user=uname dbname=udbname sslmode=disable password=supersecret&quot;)
	if err != nil {
		r.ERROR.Println(&quot;FATAL&quot;, err)
		panic( err )
	}
	Gdb.AutoMigrate(&amp;models.User{})
    // 如果需要,添加唯一索引
	//Gdb.Model(&amp;models.User{}).AddUniqueIndex(&quot;idx_user_name&quot;, &quot;name&quot;)
}


// 事务

// 在每个事务之前,此方法填充c.Txn
func (c *GormController) Begin() r.Result {
	txn := Gdb.Begin()
	if txn.Error != nil {
		panic(txn.Error)
	}
	c.Txn = txn
	return nil
}

// 在每个事务之后,此方法清除c.Txn
func (c *GormController) Commit() r.Result {
	if c.Txn == nil {
		return nil
	}
	c.Txn.Commit()
	if err := c.Txn.Error; err != nil &amp;&amp; err != sql.ErrTxDone {
		panic(err)
	}
	c.Txn = nil
	return nil
}

// 在每个事务之后,此方法也清除c.Txn
func (c *GormController) Rollback() r.Result {
	if c.Txn == nil {
		return nil
	}
	c.Txn.Rollback()
	if err := c.Txn.Error; err != nil &amp;&amp; err != sql.ErrTxDone {
		panic(err)
	}
	c.Txn = nil
	return nil
}

app.go

<!-- language: java -->

package controllers

import(
	&quot;github.com/revel/revel&quot;
	&quot;yourappname/app/models&quot;
)

type App struct {
	GormController
}

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

init.go

<!-- language: java -->

package controllers
import &quot;github.com/revel/revel&quot;

func init() {
	revel.OnAppStart(InitDB) // 在之前调用InitDB函数
	revel.InterceptMethod((*GormController).Begin, revel.BEFORE)
	revel.InterceptMethod((*GormController).Commit, revel.AFTER)
	revel.InterceptMethod((*GormController).Rollback, revel.FINALLY)
}

正如您所看到的,这类似于使用GORM修改的Revel的Booking示例。

对我来说运行良好。结果如下:

<!-- language: lang-json -->

{
  &quot;Id&quot;: 5,
  &quot;Name&quot;: &quot;Jinzhup&quot;,
  &quot;EncryptedPassword&quot;: null,
  &quot;Password&quot;: &quot;&quot;,
  &quot;CreatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;UpdatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;DeletedAt&quot;: &quot;0001-01-01T00:00:00Z&quot;
}
英文:

Important note

it's just replacement for GORP of original example of the Revel. And it comes with some pitfalls of the origin. This answer can be used as drop-in replacement for the original one. But it doesn't solve the pitfalls.

Please, take a look comments of this unswer and @MaxGabriel's answer that solves the pitfalls.

I'd recommend to use @MaxGabriel's solution to protect you application against some kinds of slow-* DDoS attacks. And to reduce (in some cases) DB pressure.

Original answer

@rauyran rights, you have to invoke InitDB inside init function (into controllers package).

Full example here (too much):

Tree

<!-- language: lang-none -->

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

user.go

<!-- language: java -->

// models/user.go
package models

import  &quot;time&quot; // if you need/want

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

gorm.go

<!-- language: java -->

//controllers/gorm.go
package controllers

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

// 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(&quot;postgres&quot;, &quot;user=uname dbname=udbname sslmode=disable password=supersecret&quot;)
	if err != nil {
		r.ERROR.Println(&quot;FATAL&quot;, err)
		panic( err )
	}
	Gdb.AutoMigrate(&amp;models.User{})
    // unique index if need
	//Gdb.Model(&amp;models.User{}).AddUniqueIndex(&quot;idx_user_name&quot;, &quot;name&quot;)
}


// 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 &amp;&amp; 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 &amp;&amp; err != sql.ErrTxDone {
		panic(err)
	}
	c.Txn = nil
	return nil
}

app.go

<!-- language: java -->

package controllers

import(
	&quot;github.com/revel/revel&quot;
	&quot;yourappname/app/models&quot;
)

type App struct {
	GormController
}

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

init.go

<!-- language: java -->

package controllers
import &quot;github.com/revel/revel&quot;

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)
}

As you can see, it's like Revel's Booking modified for GORM.

Works fine for me. Result:

<!-- language: lang-json -->

{
  &quot;Id&quot;: 5,
  &quot;Name&quot;: &quot;Jinzhup&quot;,
  &quot;EncryptedPassword&quot;: null,
  &quot;Password&quot;: &quot;&quot;,
  &quot;CreatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;UpdatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;DeletedAt&quot;: &quot;0001-01-01T00:00:00Z&quot;
}

答案2

得分: 3

这个答案是基于@IvanBlack的答案,他建议这样做。他的版本是Revel示例代码的直接翻译,但我们发现了一些原始代码中的问题,这个答案进行了修复。它所做的主要更改是:

  1. 整个HTTP请求不再被数据库事务包装。将整个HTTP请求包装在事务中会使事务保持打开的时间比必要的时间长,这可能会导致一些问题:

    • 您的事务将在数据库上持有锁,许多持有锁的事务可能导致死锁
    • 使用更多的数据库资源
    • 如果您的HTTP请求不仅仅受限于SQL数据库访问,这些问题会被放大。例如,如果您的HTTP请求在进行外部HTTP请求或访问其他数据库时阻塞了30秒钟,那么这些服务的减速可能会影响您的SQL数据库。
  2. 因为事务不再在HTTP请求结束时自动检查错误,所以错误会在数据库插入后立即检查。这也更正确:想象一下,在将User结构插入数据库后,您将User.Id存储在Redis等其他数据库中。如果数据库插入成功,这样做是可以的,但如果插入失败并且您没有立即检查错误,那么您将在Redis中插入默认的int64值0(然后稍后仅回滚SQL事务)。

Tree

<!-- language: lang-none -->

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

user.go

<!-- language: java -->

// models/user.go
package models

import  &quot;time&quot; // if you need/want

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

gorm.go

<!-- language: java -->

//controllers/gorm.go
package controllers

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

// 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(&quot;postgres&quot;, &quot;user=USERNAME dbname=DBNAME sslmode=disable&quot;)
    Gdb.LogMode(true) // Print SQL statements
    if err != nil {
        r.ERROR.Println(&quot;FATAL&quot;, err)
        panic(err)
    }
}

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

init.go

<!-- language: java -->

package controllers
import &quot;github.com/revel/revel&quot;

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

app.go

<!-- language: java -->

package controllers

import(
	&quot;github.com/revel/revel&quot;
	&quot;yourappname/app/models&quot;
)

type App struct {
	GormController
}

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

结果:

<!-- language: lang-json -->

{
  &quot;Id&quot;: 5,
  &quot;Name&quot;: &quot;Jinzhup&quot;,
  &quot;EncryptedPassword&quot;: null,
  &quot;Password&quot;: &quot;&quot;,
  &quot;CreatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;UpdatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;DeletedAt&quot;: &quot;0001-01-01T00:00:00Z&quot;
}
英文:

This answer is derived from @IvanBlack's answer, at his suggestion. His version is a direct translation of the Revel example code, but we identified some problems with the original code that this answer fixes. The main changes it makes are:

  1. The entire HTTP request is no longer wrapped by a database transaction. Wrapping the entire HTTP request keeps open the transaction far longer than necessary, which could cause a number of problems:

    • Your transactions will hold locks on the database, and many transactions holding locks could lead to deadlocks
    • More database resources are used
    • These problems are magnified if your HTTP requests aren't mostly limited by your SQL database access. E.g. if your HTTP request blocks for 30 seconds on making an external HTTP request or accessing another database, the slowdown on those services could affect your SQL database.
  2. Because the transaction is no longer automatically being checked for errors at the end of the HTTP request, errors are checked as soon as the database insert is made. This is more correct as well: Imagine that after inserting the User struct into a database, you then stored the User.Id in another database like Redis. This would be fine if the database insert worked, but if it failed and you didn't immediately check the error, you would insert the default int64 value of 0 into Redis (before later rolling back only the SQL transaction).

Tree

<!-- language: lang-none -->

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

user.go

<!-- language: java -->

// models/user.go
package models

import  &quot;time&quot; // if you need/want

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

gorm.go

<!-- language: java -->

//controllers/gorm.go
package controllers

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

// 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(&quot;postgres&quot;, &quot;user=USERNAME dbname=DBNAME sslmode=disable&quot;)
    Gdb.LogMode(true) // Print SQL statements
    if err != nil {
        r.ERROR.Println(&quot;FATAL&quot;, err)
        panic(err)
    }
}

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

init.go

<!-- language: java -->

package controllers
import &quot;github.com/revel/revel&quot;

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

app.go

<!-- language: java -->

package controllers

import(
	&quot;github.com/revel/revel&quot;
	&quot;yourappname/app/models&quot;
)

type App struct {
	GormController
}

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

Result:

<!-- language: lang-json -->

{
  &quot;Id&quot;: 5,
  &quot;Name&quot;: &quot;Jinzhup&quot;,
  &quot;EncryptedPassword&quot;: null,
  &quot;Password&quot;: &quot;&quot;,
  &quot;CreatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;UpdatedAt&quot;: &quot;2014-09-22T17:55:14.828661062+04:00&quot;,
  &quot;DeletedAt&quot;: &quot;0001-01-01T00:00:00Z&quot;
}

答案3

得分: 2

你的错误是因为你没有初始化 c.DB 数据库变量,它仍然是 nil。

在你的 controllers/init.go 文件中,确保你调用了 revel.OnAppStart(InitDB)。它应该类似于以下代码:

package controllers

import "github.com/revel/revel"

func init() {
    revel.OnAppStart(InitDB)
    // 可能还有其他的初始化操作
}
英文:

Your error is caused because you haven't initialised your c.DB database variable, it's still nil.

In your controllers/init.go file make sure you are calling revel.OnAppStart(InitDB). It should look something like this:

package controllers

import &quot;github.com/revel/revel&quot;

func init() {
    revel.OnAppStart(InitDB)
    // maybe some other init things
}

答案4

得分: -1

你需要传递一个指向AutoMigrate的指针吗?

DB.AutoMigrate(&models.User{})

英文:

OR you need to pass in a pointer to AutoMigrate?

 DB.AutoMigrate(&amp;models.User{})

huangapple
  • 本文由 发表于 2014年8月5日 23:14:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/25142372.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定