在 Golang 中构建 CRUD 应用程序
- 2022 年 6 月 12 日
- 本文字数:6679 字 - 阅读完需:约 22 分钟 

在本教程中,我们将在 Golang 中构建一个 CRUD 应用程序。我们将使用 gorilla/mux 库作为 api 和 PostgreSQL DB 来存储数据。
在 $GOPATH 之外创建一个新的项目目录 go-postgres。
在 go-postgres 项目中打开终端。实例化 go 模块。
go mod init go-postgres安装依赖
我们将在这个项目中使用 3 个包,在 go-postgres 项目中打开终端。
1. gorilla/mux router
gorilla/mux 包实现了一个请求路由器和调度程序,用于将传入请求与其各自的处理程序匹配。
go get -u github.com/gorilla/mux2. lib/pq driver
Go 的 database/sql 包的纯 Go postgres 驱动程序。
go get github.com/lib/pq我们将使用 godotenv 包来读取 .env 文件。 .env 文件用于保存环境变量。环境变量用于保护敏感数据的安全。
go get github.com/joho/godotenv现在,打开 go.mod 并检查。所有已安装的依赖项都列出了已安装的版本。
与此类似,版本可以不同。
module go-postgres
require (    github.com/gorilla/mux v1.7.4    github.com/joho/godotenv v1.3.0    github.com/lib/pq v1.3.0)安装 Postgres
PostgreSQL 是一个功能强大的开源对象关系数据库系统。它以可靠性、功能稳健性和性能而闻名。
创建实例后。现在,我们必须创建一个表。转到 ElephantSQL 中的 Browser 选项卡并粘贴下面的创建表查询并执行它。我们使用 SERIAL 类型作为用户 ID。 SERIAL 每次插入操作都会自动递增。
CREATE TABLE users (    userid SERIAL PRIMARY KEY,    name TEXT,    age INT,    location TEXT);项目目录结构
该项目分为 4 个部分,以保持代码模块化和干净。
目录结构为:
|- go-postgres    |- middleware        |- handlers.go    |- models        |- models.go    |- router        |- router.go    |- .env    |- main.go模型
模型包将存储数据库模式。我们将使用 struct 类型来表示或映射 golang 中的数据库模式。
在 go-postgres 项目中创建一个新的文件夹模型。
在模型中创建一个新文件 models.go 并粘贴以下代码。
package models
// User schema of the user tabletype User struct {    ID       int64  `json:"id"`    Name     string `json:"name"`    Location string `json:"location"`    Age      int64  `json:"age"`}User 结构是我们在上面创建的 users 表的表示。
中间件
中间件包是 API 和数据库之间的桥梁。这个包将处理所有的数据库操作,如插入、选择、更新和删除 (CRUD)。
创建一个新文件夹 middleware 并在其中创建一个新文件 handlers.go。
粘贴以下代码。
package middleware
import (    "database/sql"    "encoding/json" // package to encode and decode the json into struct and vice versa    "fmt"    "go-postgres/models" // models package where User schema is defined    "log"    "net/http" // used to access the request and response object of the api    "os"       // used to read the environment variable    "strconv"  // package used to covert string into int type
    "github.com/gorilla/mux" // used to get the params from the route
    "github.com/joho/godotenv" // package used to read the .env file    _ "github.com/lib/pq"      // postgres golang driver)
// response formattype response struct {    ID      int64  `json:"id,omitempty"`    Message string `json:"message,omitempty"`}
// create connection with postgres dbfunc createConnection() *sql.DB {    // load .env file    err := godotenv.Load(".env")
    if err != nil {        log.Fatalf("Error loading .env file")    }
    // Open the connection    db, err := sql.Open("postgres", os.Getenv("POSTGRES_URL"))
    if err != nil {        panic(err)    }
    // check the connection    err = db.Ping()
    if err != nil {        panic(err)    }
    fmt.Println("Successfully connected!")    // return the connection    return db}
// CreateUser create a user in the postgres dbfunc CreateUser(w http.ResponseWriter, r *http.Request) {    // set the header to content type x-www-form-urlencoded    // Allow all origin to handle cors issue    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")    w.Header().Set("Access-Control-Allow-Origin", "*")    w.Header().Set("Access-Control-Allow-Methods", "POST")    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    // create an empty user of type models.User    var user models.User
    // decode the json request to user    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {        log.Fatalf("Unable to decode the request body.  %v", err)    }
    // call insert user function and pass the user    insertID := insertUser(user)
    // format a response object    res := response{        ID:      insertID,        Message: "User created successfully",    }
    // send the response    json.NewEncoder(w).Encode(res)}
// GetUser will return a single user by its idfunc GetUser(w http.ResponseWriter, r *http.Request) {    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")    w.Header().Set("Access-Control-Allow-Origin", "*")    // get the userid from the request params, key is "id"    params := mux.Vars(r)
    // convert the id type from string to int    id, err := strconv.Atoi(params["id"])
    if err != nil {        log.Fatalf("Unable to convert the string into int.  %v", err)    }
    // call the getUser function with user id to retrieve a single user    user, err := getUser(int64(id))
    if err != nil {        log.Fatalf("Unable to get user. %v", err)    }
    // send the response    json.NewEncoder(w).Encode(user)}
// GetAllUser will return all the usersfunc GetAllUser(w http.ResponseWriter, r *http.Request) {    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")    w.Header().Set("Access-Control-Allow-Origin", "*")    // get all the users in the db    users, err := getAllUsers()
    if err != nil {        log.Fatalf("Unable to get all user. %v", err)    }
    // send all the users as response    json.NewEncoder(w).Encode(users)}
// UpdateUser update user's detail in the postgres dbfunc UpdateUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/x-www-form-urlencoded")    w.Header().Set("Access-Control-Allow-Origin", "*")    w.Header().Set("Access-Control-Allow-Methods", "PUT")    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    // get the userid from the request params, key is "id"    params := mux.Vars(r)
    // convert the id type from string to int    id, err := strconv.Atoi(params["id"])
    if err != nil {        log.Fatalf("Unable to convert the string into int.  %v", err)    }
    // create an empty user of type models.User    var user models.User
    // decode the json request to user    err = json.NewDecoder(r.Body).Decode(&user)
    if err != nil {        log.Fatalf("Unable to decode the request body.  %v", err)    }
    // call update user to update the user    updatedRows := updateUser(int64(id), user)
    // format the message string    msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", updatedRows)
    // format the response message    res := response{        ID:      int64(id),        Message: msg,    }
    // send the response    json.NewEncoder(w).Encode(res)}
// DeleteUser delete user's detail in the postgres dbfunc DeleteUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Context-Type", "application/x-www-form-urlencoded")    w.Header().Set("Access-Control-Allow-Origin", "*")    w.Header().Set("Access-Control-Allow-Methods", "DELETE")    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    // get the userid from the request params, key is "id"    params := mux.Vars(r)
    // convert the id in string to int    id, err := strconv.Atoi(params["id"])
    if err != nil {        log.Fatalf("Unable to convert the string into int.  %v", err)    }
    // call the deleteUser, convert the int to int64    deletedRows := deleteUser(int64(id))
    // format the message string    msg := fmt.Sprintf("User updated successfully. Total rows/record affected %v", deletedRows)
    // format the reponse message    res := response{        ID:      int64(id),        Message: msg,    }
    // send the response    json.NewEncoder(w).Encode(res)}
//------------------------- handler functions ----------------// insert one user in the DBfunc insertUser(user models.User) int64 {
    // create the postgres db connection    db := createConnection()
    // close the db connection    defer db.Close()
    // create the insert sql query    // returning userid will return the id of the inserted user    sqlStatement := `INSERT INTO users (name, location, age) VALUES ($1, $2, $3) RETURNING userid`
    // the inserted id will store in this id    var id int64
    // execute the sql statement    // Scan function will save the insert id in the id    err := db.QueryRow(sqlStatement, user.Name, user.Location, user.Age).Scan(&id)
    if err != nil {        log.Fatalf("Unable to execute the query. %v", err)    }
    fmt.Printf("Inserted a single record %v", id)
    // return the inserted id    return id}
// get one user from the DB by its useridfunc getUser(id int64) (models.User, error) {    // create the postgres db connection    db := createConnection()
    // close the db connection    defer db.Close()
    // create a user of models.User type    var user models.User
    // create the select sql query    sqlStatement := `SELECT * FROM users WHERE userid=$1`
    // execute the sql statement    row := db.QueryRow(sqlStatement, id)
    // unmarshal the row object to user    err := row.Scan(&user.ID, &user.Name, &user.Age, &user.Location)
    switch err {    case sql.ErrNoRows:        fmt.Println("No rows were returned!")        return user, nil    case nil:        return user, nil    default:        log.Fatalf("Unable to scan the row. %v", err)    }
    // return empty user on error    return user, err}
// get one user from the DB by its useridfunc getAllUsers() ([]models.User, error) {    // create the postgres db connection    db := createConnection()
    // close the db connection    defer db.Close()
    var users []models.User
    // create the select sql query    sqlStatement := `SELECT * FROM users`
    // execute the sql statement    rows, err := db.Query(sqlStatement)
    if err != nil {        log.Fatalf("Unable to execute the query. %v", err)    }
    // close the statement    defer rows.Close()
    // iterate over the rows    for rows.Next() {        var user models.User
        // unmarshal the row object to user        err = rows.Scan(&user.ID, &user.Name, &user.Age, &user.Location)
        if err != nil {            log.Fatalf("Unable to scan the row. %v", err)        }
        // append the user in the users slice        users = append(users, user)
    }
    // return empty user on error    return users, err}
// update user in the DBfunc updateUser(id int64, user models.User) int64 {
    // create the postgres db connection    db := createConnection()
    // close the db connection    defer db.Close()
    // create the update sql query    sqlStatement := `UPDATE users SET name=$2, location=$3, age=$4 WHERE userid=$1`
    // execute the sql statement    res, err := db.Exec(sqlStatement, id, user.Name, user.Location, user.Age)
    if err != nil {        log.Fatalf("Unable to execute the query. %v", err)    }
    // check how many rows affected    rowsAffected, err := res.RowsAffected()
    if err != nil {        log.Fatalf("Error while checking the affected rows. %v", err)    }
    fmt.Printf("Total rows/record affected %v", rowsAffected)
    return rowsAffected}
// delete user in the DBfunc deleteUser(id int64) int64 {
    // create the postgres db connection    db := createConnection()
    // close the db connection    defer db.Close()
    // create the delete sql query    sqlStatement := `DELETE FROM users WHERE userid=$1`
    // execute the sql statement    res, err := db.Exec(sqlStatement, id)
    if err != nil {        log.Fatalf("Unable to execute the query. %v", err)    }
    // check how many rows affected    rowsAffected, err := res.RowsAffected()
    if err != nil {        log.Fatalf("Error while checking the affected rows. %v", err)    }
    fmt.Printf("Total rows/record affected %v", rowsAffected)
    return rowsAffected}让我们分解功能:
- createConnection:此函数将创建与 postgreSQL 数据库的连接并返回数据库连接。
检查函数中的代码:
// use godotenv to load the .env fileerr := godotenv.Load(".env")
// Read the POSTGRES_URL from the .env and connect to the db.db, err := sql.Open("postgres", os.Getenv("POSTGRES_URL"))在 go-postgres 中创建一个新文件 .env:
POSTGRES_URL="Postgres connection string"- CreateUser:这是可以访问 api 的请求和响应对象的处理函数。它将在用户中提取请求正文。然后,它会调用 insertUser 作为参数传递用户。 insertUser 将返回插入 id
- insertUser:此函数将在数据库中执行插入查询。首先建立 db 连接。
// create the postgres db connectiondb := createConnection()
// close the db connectiondefer db.Close()创建 SQL 查询:
sqlStatement := `INSERT INTO users (name, location, age) VALUES ($1, $2, $3) RETURNING userid`我们没有传递用户 ID,因为用户 ID 是 SERIAL 类型。它的范围是 1 到 2,147,483,647。
每次插入都会增加。
RETURNING userid 意味着一旦在数据库中成功插入,就返回用户 ID。
执行插入查询
var id int64err := db.QueryRow(sqlStatement, user.Name, user.Location, user.Age).Scan(&id)在 QueryRow 中接受 sql 查询和参数。在 sqlStatement 中,VALUES 作为变量 $1、$2、$3 传递。 user.Name 是第一个参数,因此它将替换 $1。同样,所有参数都将根据它们的位置进行替换。
使用扫描返回用户 ID 将解码为 id
版权声明: 本文为 InfoQ 作者【宇宙之一粟】的原创文章。
原文链接:【http://xie.infoq.cn/article/5e15cb85ff9dc64f03b843f1d】。文章转载请联系作者。


宇宙之一粟
宇宙古今无有穷期,一生不过须臾,当思奋争 2020.05.07 加入
🏆InfoQ写作平台-第二季签约作者 🏆 混迹于江湖,江湖却没有我的影子 热爱技术,专注于后端全栈,轻易不换岗 拒绝内卷,工作于软件工程师,弹性不加班 热衷分享,执着于阅读写作,佛系不水文










 
    
评论