dongya1875 2016-04-01 02:26
浏览 93
已采纳

Go-变量已初始化且非nil,其他函数则为nil

It's a bit hard to write a title that match with my current problem.. I have a main() function which uses a function in another package (database_sql). This function initializes a global variable sql.DB*. After the initialization, the variable isn't nil, but for the others functions, this variable is still nil.. Let see the code below !

main.go

package main

import (
    "net/http"
    db "./database_sql"
    router "./router"
)

func main() {

    db.Init_SQL();

    router.Init_routes()

    http.ListenAndServe(":8080", router.GetRouter())

}

db.go

package database_sql

import (
    "fmt"
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

var DB *sql.DB // global variable to share it between main and the HTTP handler                                                                                                                              
//var dbi *databaseinfos                                                                                                                                                                                     

func Init_SQL() {
    dbi := databaseinfos{
            user: "something",
            password: "something",
            name: "something",
            address: "something",
            port: "2323",
            url: ""}
    dbi.url = (dbi.user + ":" + dbi.password + "@tcp(" + dbi.address + ":" + dbi.port + ")/" + dbi.name)

    db_init_var, err := sql.Open("mysql", dbi.url)
    if err != nil {
            fmt.Println("Error on initializing database connection: %s", err.Error())
    }

    err = db_init_var.Ping()
    if err != nil {
            fmt.Println("Error on opening database connection: %s", err.Error())
    }

    // Here, as you can test, my variable is initialized
    if err == nil {
            DB = db_init_var
            if DB == nil {
                    fmt.Println("NIL DB !!")
            }
            if db_init_var == nil {
                    fmt.Println("NIL db_init_var !!")
            }
            fmt.Println(dbi.url)
    }
}

Now, everything is ok ! I will now test http://xxxxxxx/login with an username/password. Everything's working fine and now, it's the time to make a request in the database with my DB variable declared above.

request_user.go

package database_sql

import (
    "encoding/json"
    "fmt"
    "net/http"
    m "models"
)

func CanLogin(user m.User) (bool, m.User, m.StatusBack) {

    // Prepare statement for reading data                                                                                                                                                                
    stmtOut, err := DB.Prepare("SELECT username, password, token FROM users WHERE username = ? AND password = ?")
    if err != nil {
            return false, user, m.StatusBack{ToString: err.Error(), IdStatus: http.StatusInternalServerError}
    }
defer stmtOut.Close()

    // Query the user                                                                                                                                                                                    
err = stmtOut.QueryRow(user.Username, user.Password).Scan(&user.Username, &user.Password, &user.UUID) // WHERE user is registered                                                                    
    if err != nil {
            return false, user, m.StatusBack{ToString: "User not found.", IdStatus: http.StatusNotFound}
    } else {

            j, err := json.Marshal(user)
            if err == nil {
                    return true, user, m.StatusBack{ToString: string(j), IdStatus: http.StatusOK}
            } else {
                    return false, user, m.StatusBack{ToString: err.Error(), IdStatus: http.StatusInternalServerError}
    }
    }

}

However, when I'm making my sql request, the following line says that DB is nil. stmtOut, err := DB.Prepare("SELECT username, password, token FROM users WHERE username = ? AND password = ?")

I made lot of test such as trying to initialize it with '=' and no ':='. I tried 'if DB == nil' statement and DB is always nil.. Why? I initialize my database var before initialize my router, so technically, when my router give a redirection to a function, this function wouldn't use a database var (DB) nil, isn't?

Thank for your answers !

EDIT 2 I red your answers and then, I edited my code like you asked with some adds. After init_SQL, DB isn't nil !

main.go

package main

import (
    "fmt"
    "net/http"
    db "./database_sql"
    router "./router"
)

func main() {

    if db.Init_SQL() == true {

            if db.DB == nil {
                    fmt.Println("db.DB is nil !")
            }

            if router.Init_routes() == true {

                    http.ListenAndServe(":8080", router.GetRouter())

            }
    }
}

So, now my 2 init functions return true on success and false on failure. I thought it could be an asynchronous problem, I made it like that to wait the end of each functions to continue my 'step by step'

  • 写回答

1条回答 默认 最新

  • douao2000 2016-04-01 14:12
    关注

    First, it is not idomatic to export a global var like that. Complex types should be initialized by the runtime (which you are doing with main()), but retained globally from the runtime to control the disposal.

    You are also missing a db.Close().

    I believe I ran I to the same issue with your pattern a few years ago when first using MySQL. There is an oddness with the way one creates a local scoped pointer, and then assign it to a global var. It is usually better to assign it directly to global var. But I think the core issue is you need to assign the pointer to the pointer.

    The pattern I use is to keep the testable database/sql *DB in the global state where I initialize it. Why reinvent the wheel when the wheel works:

    package main
    
    import ( 
      ...
      "database/sql"
    
      "mypackage-that-inits-db/database"
    )
    
    var db *sql.DB
    
    find main() {
    
      var err error
      db, err = database.Init_SQL(...params)
      if err != nil {
        ...
      }
      defer db.Close()
    
      InitUsers(db)
      InitStats(db)
      ...
    
      http.ListenAndServe(...)
    
      // now inject db global dependency in other code
      //
    
      globalStats := NewStatsEngine(db)
      globalStats.RecordUpdateToSomething(...)
    
      users := getUsers(db)
    
       ... etc
    
    }
    

    That's typically the pattern you see in other code.

    Note the control over defer and Close, where it belongs here in caller.

    Can't Test that easily

    Also note that your code in other packages now becomes easily testable by creating a mock sql.DB object and injecting it into NewStatsEngine, getUsers, etc instead of having to mock a global var, test, tear down and setup again for test test, test, teardown, etc. Also since Go 1.5, tests could be run concurrently so you'll even need to put a mutex.Lock around your global DB var if you leave your package like that. Switching to a IoC pattern, like Dependency Injection which I demo'd in the code above, makes so much easier to test (and debug) your code.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 asp.textbox后台赋值前端不能显示什么原因
  • ¥15 宇视监控服务器无法登录
  • ¥15 PADS Logic 原理图
  • ¥15 PADS Logic 图标
  • ¥15 电脑和power bi环境都是英文如何将日期层次结构转换成英文
  • ¥15 DruidDataSource一直closing
  • ¥20 气象站点数据求取中~
  • ¥15 如何获取APP内弹出的网址链接
  • ¥15 wifi 图标不见了 不知道怎么办 上不了网 变成小地球了
  • ¥50 STM32单片机传感器读取错误