Golang處理數據庫的nil數據


在用golang獲取數據庫的數據的時候,難免會遇到可控field。這個時候拿到的數據如果直接用string, time.Time這樣的類型來解析的話會遇到panic。

那么如何處理這個問題呢,第一個出現在眼前的辦法就是用database/sql。這個包里包含了很多的可以處理可控字段的類型,比如:sql.NullString, sql.NullBool等。所以,model可以用這些類型來定義,如:

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用的是普通的類型。

所以,寫起來是這樣的:

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:

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是不能做類型轉換的,比如:

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代碼:


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
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM