Golang處理數據庫的nil數據

在用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
}
相關文章
相關標籤/搜索