无效的内存地址或空指针解引用与sql.DB相关。

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

invalid memory address or nil pointer dereference with sql.DB

问题

我目前正在学习Go,并尝试创建一个小的SQL工具集:

type DBUtils struct {
  User string
  Password string
  Host string
  Database string
  Handle *sql.DB
}

func (dbUtil DBUtils) Connect() {
  var err error
  dbUtil.Handle, err = sql.Open("mysql", dbUtil.User + ":" + dbUtil.Password + "@tcp(" + dbUtil.Host + ")/" + dbUtil.Database)
  if err != nil {
    panic(err.Error()) 
  }

  err = dbUtil.Handle.Ping()
  if err != nil {
    panic(err.Error())
  }

  fmt.Printf("%v", dbUtil)
}

func (dbUtil DBUtils) Close() {
  dbUtil.Handle.Close()
}

func (dbUtil DBUtils) GetString(what string, from string, where string, wherevalue string) string {
  var username string

  fmt.Printf("%v", dbUtil)

  stmtOut, err := dbUtil.Handle.Prepare("SELECT " + what + " FROM " + from + " WHERE " + where + " = " + wherevalue)
  if err != nil {
      panic(err.Error()) // proper error handling instead of panic in your app
  }

  err = stmtOut.QueryRow(1).Scan(&username)

  return username
}

当使用以下代码时:

db := databaseutils.DBUtils{"root", "root", "127.0.0.1:3306", "gotest", nil}    
db.Connect() // 我得到:{root root 127.0.0.1:3306 gotest 0xc42019d600}
fmt.Printf("%v", db) // 我得到 {root root 127.0.0.1:3306 gotest <nil>}
x := db.GetString("username", "users", "id", "1") // 不起作用:panic: runtime error: invalid memory address or nil pointer dereference
fmt.Println(x)

对我来说,似乎我的数据库句柄没有正确保存?有人有什么想法吗?我对Go还很陌生,它与PHP、JS、C++等看起来有很多不同之处。

提前感谢!

英文:

I'm learning Go at the moment and trying to make a little SQL-toolset:

type DBUtils struct {
  User string
  Password string
  Host string
  Database string
  Handle *sql.DB
}

func (dbUtil DBUtils) Connect() {
  var err error
  dbUtil.Handle, err = sql.Open(&quot;mysql&quot;, dbUtil.User + &quot;:&quot; + dbUtil.Password + &quot;@tcp(&quot; + dbUtil.Host + &quot;)/&quot; + dbUtil.Database)
  if err != nil {
    panic(err.Error()) 
  }

  err = dbUtil.Handle.Ping()
  if err != nil {
    panic(err.Error())
  }

  fmt.Printf(&quot;%v&quot;, dbUtil)
}

func (dbUtil DBUtils) Close() {
  dbUtil.Handle.Close()
}

func (dbUtil DBUtils) GetString(what string, from string, where string, wherevalue string) string {
  var username string

  fmt.Printf(&quot;%v&quot;, dbUtil)

  stmtOut, err := dbUtil.Handle.Prepare(&quot;SELECT &quot; + what + &quot; FROM &quot; + from + &quot; WHERE &quot; + where + &quot; = &quot; + wherevalue)
  if err != nil {
      panic(err.Error()) // proper error handling instead of panic in your app
  }

  err = stmtOut.QueryRow(1).Scan(&amp;username)

  return username
}

So when using this with the following code:

db := databaseutils.DBUtils{&quot;root&quot;, &quot;root&quot;, &quot;127.0.0.1:3306&quot;, &quot;gotest&quot;, nil}    
db.Connect() // I get: {root root 127.0.0.1:3306 gotest 0xc42019d600}
fmt.Printf(&quot;%v&quot;, db) // I get {root root 127.0.0.1:3306 gotest &lt;nil&gt;}
x := db.GetString(&quot;username&quot;, &quot;users&quot;, &quot;id&quot;, &quot;1&quot;) // Doesn&#39;t work: panic: runtime error: invalid memory address or nil pointer dereference
fmt.Println(x)

For me it seems like my DB handle isn't saved properly? Does anyone have an idea - I'm pretty new to go and there are many things looking different to PHP, JS, C++ etc.

Thanks in advance!

答案1

得分: 2

你的Connect方法没有改变你调用该方法的对象的状态。你正在处理的是类型的副本。如果你想要一个方法来改变对象本身,你需要在指针上定义它:

func (dbUtil *DBUtils) Connect() {
    //instead of
    func (dbUtil DBUtils) Connect() {

如果你熟悉C或C++,你当前的方法类似于以下代码:

void connect_db(struct db_utils db_util)
{}

当你调用这样的函数时,你创建了一个参数的副本,并将其推入堆栈。connect_db函数将使用该副本进行操作,在返回后,副本将被释放。

将其与以下C代码进行比较:

struct foo {
    int bar;
};

static
void change_copy(struct foo bar)
{
    bar.bar *= 2;
}

static
void change_ptr(struct foo *bar)
{
    bar->bar *= 2;
}

int main ( void )
{
    struct foo bar = {10};
    printf("%d\n", bar.bar);//prints 10
    change_copy(bar);//pass by value
    printf("%d\n", bar.bar);//still prints 10
    change_ptr(&bar);
    printf("%d\n", bar.bar);//prints 20
    return 0;
}

在Go中也是同样的情况。你在方法上定义的对象只能在有权访问该实例的情况下更改实例的状态。否则,它无法更新内存的那部分。

如果你想知道,这个方法不需要在指针类型上定义:

func (dbUtil DBUtils) Close() {
    dbUtil.Handle.Close()
}

原因是DBUtils.Handle是一个指针类型。该指针的副本始终指向相同的资源。

不过,我要说的是,考虑到你实际上是在封装句柄,并且你暴露了ConnectClose方法,成员本身实际上不应该被导出。我会将其更改为小写的handle

英文:

Your Connect method is not changing the state of the object you're calling the method on. You're working on a copy of the type. If you want a method to change the object itself, you'll have to define it on a pointer:

func (dbUtil *DBUtils) Connect() {
//instead of
func (dbUtil DBUtils) Connect() {

If you're familiar with C or C++, your current method works similarly to something like:

void connect_db(struct db_utils db_util)
{}

When you call a function like that, you're creating a copy of the argument, and push that on to the stack. The connect_db function will work with that, and after it returns, the copy is deallocated.

compare it to this C code:

struct foo {
    int bar;
};
static
void change_copy(struct foo bar)
{
    bar.bar *= 2;
}

static
void change_ptr(struct foo *bar)
{
    bar-&gt;bar *= 2;
}

int main ( void )
{
    struct foo bar = {10};
    printf(&quot;%d\n&quot;, bar.bar);//prints 10
    change_copy(bar);//pass by value
    printf(&quot;%d\n&quot;, bar.bar);//still prints 10
    change_ptr(&amp;bar);
    printf(&quot;%d\n&quot;, bar.bar);//prints 20
    return 0;
}

The same thing happens in go. The object on which you define the method can only change state of the instance if it has access to the instance. If not, it can't update that part of the memory.

In case you're wondering, this method doesn't need to be defined on the pointer type:

func (dbUtil DBUtils) Close() {
  dbUtil.Handle.Close()
}

The reason for this being that DBUtils.Handle is a pointer type. A copy of that pointer will always point to the same resource.

I will say this, though: given that you're essentially wrapping the handle, and you're exposing the Connect and Close methods, the member itself really shouldn't be exported. I'd change it to lower-case handle

答案2

得分: 1

你关于DB句柄未保存的观察是正确的。你是在一个值接收器上定义你的方法,因此在Connect方法内部接收到一个副本并对其进行修改。然后,在Connect方法结束时,该副本被丢弃。

你需要在指向你的结构体的指针上定义你的方法:

func (dbUtil *DBUtils) Connect() {
  var err error
  dbUtil.Handle, err = sql.Open("mysql", dbUtil.User + ":" + dbUtil.Password + "@tcp(" + dbUtil.Host + ")/" + dbUtil.Database)
  if err != nil {
    panic(err.Error()) 
  }

  err = dbUtil.Handle.Ping()
  if err != nil {
    panic(err.Error())
  }

  fmt.Printf("%v", dbUtil)
}

更多信息请参考:https://golang.org/doc/faq#methods_on_values_or_pointers。

注意:我注意到你使用了QueryRow。它接受准备语句中参数的参数。你那里没有参数,所以我认为你不应该传递任何参数。你还应该检查Scan方法的结果是否有错误(参见https://golang.org/pkg/database/sql/#Stmt)。

英文:

You are right about your DB handle not being saved. You are defining your methods on a value receiver. Therefore you receive a copy inside the Connect method and modify said copy. This copy is then dropped at the end of the Connect method.

You have to define your method on a pointer to your structure:

func (dbUtil *DBUtils) Connect() {
  var err error
  dbUtil.Handle, err = sql.Open(&quot;mysql&quot;, dbUtil.User + &quot;:&quot; + dbUtil.Password + &quot;@tcp(&quot; + dbUtil.Host + &quot;)/&quot; + dbUtil.Database)
  if err != nil {
    panic(err.Error()) 
  }

  err = dbUtil.Handle.Ping()
  if err != nil {
    panic(err.Error())
  }

  fmt.Printf(&quot;%v&quot;, dbUtil)
}

For further information see https://golang.org/doc/faq#methods_on_values_or_pointers.

Note: I noticed your usage of QueryRow. It accepts arguments for parameters in your prepare statement. You have no parameters there, so I think you should not pass any parameters. You should also check the Scan result for errors (see https://golang.org/pkg/database/sql/#Stmt).

huangapple
  • 本文由 发表于 2016年11月4日 19:12:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/40421204.html
匿名

发表评论

匿名网友

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

确定