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.

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

报告相同问题?

悬赏问题

  • ¥17 pro*C预编译“闪回查询”报错SCN不能识别
  • ¥15 微信会员卡接入微信支付商户号收款
  • ¥15 如何获取烟草零售终端数据
  • ¥15 数学建模招标中位数问题
  • ¥15 phython路径名过长报错 不知道什么问题
  • ¥15 深度学习中模型转换该怎么实现
  • ¥15 Stata外部命令安装问题求帮助!
  • ¥15 从键盘随机输入A-H中的一串字符串,用七段数码管方法进行绘制。提交代码及运行截图。
  • ¥15 TYPCE母转母,插入认方向
  • ¥15 如何用python向钉钉机器人发送可以放大的图片?