在用golang获取数据库的数据的时候,不免会遇到可控field。这个时候拿到的数据若是直接用string
, time.Time
这样的类型来解析的话会遇到panic。mysql
那么如何处理这个问题呢,第一个出如今眼前的办法就是用database/sql
。这个包里包含了不少的能够处理可控字段的类型,好比:sql.NullString
, sql.NullBool
等。因此,model能够用这些类型来定义,如:git
package main import ( "database/sql" "fmt" "github.com/go-sql-driver/mysql" ) type Article struct { Id int `json:"id"` Title string `json:"title"` PubDate mysql.NullTime `json:"pub_date"` Body sql.NullString `json:"body"` User sql.NullInt64 `json:"user"` }
这样的定义是能够work的,可是会有一点奇怪:没谁会用数据库的类型来代替平时的类型。因此,咱们能够改一种思路。在解析数据库的时候会有专用的数据库类型字段来接收,可是在返回json的时候有专门的model来使用,这个model用的是普通的类型。github
因此,写起来是这样的:golang
var person Person var personID int64 var password sql.NullString var lastLogin mysql.NullTime var isSuperuser sql.NullBool var userName sql.NullString var firstName sql.NullString var lastName sql.NullString var email sql.NullString var isStaff sql.NullBool var isActive sql.NullBool var dateJoined mysql.NullTime err = ret.Scan( &personID, &password, &lastLogin, &isSuperuser, &userName, &firstName, &lastName, &email, &isStaff, &isActive, &dateJoined, )
上面定义了解析,下面定义model:sql
type Person struct { ID int64 `json:"id"` Password string `json:"password"` LastLogin *time.Time `json:"last_login"` IsSuperuser bool `json:"is_superuser"` Username string `json:"username"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` IsStaff bool `json:"is_staff"` IsActive bool `json:"is_active"` DateJoined *time.Time `json:"date_joined"` }
有了前两部以后,如今能够装配这些数据了:数据库
person.ID = personID person.Password = If(password.Valid, password.String, "").(string) if tempTime, ok := If(lastLogin.Valid, lastLogin.Time, nil).(*time.Time); ok { person.LastLogin = tempTime } else { person.LastLogin = nil } person.IsSuperuser = If(isSuperuser.Valid, isSuperuser.Bool, false).(bool) person.Username = If(userName.Valid, userName.String, "").(string) person.FirstName = If(firstName.Valid, firstName.String, "").(string) person.LastName = If(lastName.Valid, lastName.String, "").(string) person.Email = If(email.Valid, email.String, "").(string) person.IsStaff = If(isStaff.Valid, isStaff.Bool, false).(bool) person.IsActive = If(isActive.Valid, isActive.Bool, false).(bool) if tempTime, ok := If(dateJoined.Valid, dateJoined.Time, nil).(*time.Time); ok { person.DateJoined = tempTime } else { person.DateJoined = nil }
有一点须要注意的。在golang里类型转换以前须要先作一个type assertion才行,不然报错。并且nil
是不能作类型转换的,好比:json
if tempTime, ok := If(dateJoined.Valid, dateJoined.Time, nil).(*time.Time); ok { person.DateJoined = tempTime } else { person.DateJoined = nil }
以上是同sqlite作为数据库是遇到的问题。还有一点,sqlite没有处理时间为空的类型,因此上面使用的是mysql的driver里的NullTime
,奇怪的是用自定义的NullTime不行。懒得深究了,哪位知道的话但愿留言。app
下面是所有的app代码:this
package main import ( "encoding/json" "fmt" "net/http" "time" "database/sql" "log" "github.com/go-sql-driver/mysql" "github.com/labstack/echo" "github.com/labstack/echo/middleware" _ "github.com/mattn/go-sqlite3" ) func main() { // Echo instance e := echo.New() // Middleware e.Use(middleware.Logger()) e.Use(middleware.Recover()) // Routes e.GET("/", hello) // Start server e.Logger.Fatal(e.Start(":1323")) } func hello(c echo.Context) error { ret, err := executeSQL("select id,password,last_login,is_superuser,username,first_name,last_name,email,is_staff,is_active,date_joined from person where id = ?") if err != nil { return c.JSON(http.StatusOK, map[string]string{"error": "Something went wrong when getting data from db"}) } return c.JSON(http.StatusOK, ret) } // Execute sql statement from parameter, which looks like this: // select a, b, c from some_tabble where id = ? // Return a map func executeSQL(sqlStmt string) ([]Person, error) { db, err := sql.Open("sqlite3", "./db.sqlite3") if err != nil { log.Fatal(err) } defer db.Close() stmt, err := db.Prepare(sqlStmt) if err != nil { log.Fatal(err) return nil, err } defer stmt.Close() ret, err := stmt.Query(3) if err != nil { log.Fatal(err) return nil, err } personList := make([]Person, 0) for ret.Next() { var person Person var personID int64 var password sql.NullString var lastLogin mysql.NullTime var isSuperuser sql.NullBool var userName sql.NullString var firstName sql.NullString var lastName sql.NullString var email sql.NullString var isStaff sql.NullBool var isActive sql.NullBool var dateJoined mysql.NullTime err = ret.Scan( &personID, &password, &lastLogin, &isSuperuser, &userName, &firstName, &lastName, &email, &isStaff, &isActive, &dateJoined, ) if err != nil { log.Fatal(err) } person.ID = personID person.Password = If(password.Valid, password.String, "").(string) if tempTime, ok := If(lastLogin.Valid, lastLogin.Time, nil).(*time.Time); ok { person.LastLogin = tempTime } else { person.LastLogin = nil } person.IsSuperuser = If(isSuperuser.Valid, isSuperuser.Bool, false).(bool) person.Username = If(userName.Valid, userName.String, "").(string) person.FirstName = If(firstName.Valid, firstName.String, "").(string) person.LastName = If(lastName.Valid, lastName.String, "").(string) person.Email = If(email.Valid, email.String, "").(string) person.IsStaff = If(isStaff.Valid, isStaff.Bool, false).(bool) person.IsActive = If(isActive.Valid, isActive.Bool, false).(bool) if tempTime, ok := If(dateJoined.Valid, dateJoined.Time, nil).(*time.Time); ok { person.DateJoined = tempTime } else { person.DateJoined = nil } personList = append(personList, person) } j, err := json.Marshal(personList) if err != nil { log.Fatal(err) } fmt.Println(j) return personList, nil }