在Golang中使用SQL数据库

1,053 阅读14分钟

十二要素应用方法论中的进程原则主张,应用程序作为无状态进程执行。这意味着需要持久化的数据应该存储在外部支持服务中,如数据库。

当把这个原则应用到你的Golang编写的应用程序时,你可能需要使用SQL数据库来持久化数据。

在本教程中,我们将学习如何建立一个用Golang编写的基于控制台的提醒应用程序,并使用SQL数据库。

我们将专注于微软的SQL服务器,并将Golang应用程序连接到SQL服务器内的数据库,以存储用户的提醒信息。

Golang和SQL的先决条件

要跟随建立一个控制台应用程序,建议你具备以下条件。

  • 对Go编程语言有基本了解
  • 在你的本地计算机上安装Golang
  • 在你的本地计算机上安装了Docker

使用微软的SQL服务器

Microsoft SQL Server是一个关系型数据库管理系统(RDBMS),包含几个组件。其中一个组件是数据库引擎,用于管理存储在SQL Server中的数据。

在本教程中,我们将使用SQL Server的数据库引擎组件。为了建立与数据库引擎的连接,我们需要 Golang 标准库中的数据库包和go-mssqldb 包。

为了开始学习,让我们把SQL Server实例作为Docker镜像安装在本地计算机上。

安装微软的SQL服务器

现在我们可以通过计算机终端的Docker镜像来 安装Microsoft SQLServer实例

从终端执行下面的Dockerpull 命令,从DockerHub中提取2019年微软SQL服务器实例的容器镜像。

docker pull mcr.microsoft.com/mssql/server:2019-latest

接下来,执行下面的Docker运行命令,用Docker运行拉来的容器镜像。下面的命令包含参数,这些参数作为环境变量传入容器镜像,以配置容器。

请确保将命令中的INSTANCE_PASSWORD 占位符改为更安全的密码,并记住该密码,因为我们以后会用到它。

sudo docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=INSTANCE_PASSWORD" \
   -p 1433:1433 --name sql1 -h sql1 \
   -d mcr.microsoft.com/mssql/server:2019-latest

你可以进一步执行docker ps 命令来查看在后台运行的MSSQL容器。

Docker Container MSSQL Instance In terminal

创建一个SQL Server数据库

在上一节中,我们拉出了Microsoft SQL Server的镜像来运行SQL服务器实例。现在,让我们继续为我们的Golang应用程序在SQL Server实例中创建一个数据库。

要创建一个数据库,你可以使用数据库GUI工具,如SQL Server Management Studio(SSMS),或者通过sqlcmd CLI工具

我们将继续通过连接到运行SQL服务器实例的Docker镜像,从终端创建一个数据库。

首先,执行下面的Dockerexec 命令,在运行SQL服务器实例的Docker容器中启动一个Bash shell。这一步使我们能够访问容器中的sqlcmd工具。

sudo docker exec -it sql1 "bash"

接下来,通过在上面启动的交互式Bash shell中执行下面的命令来连接sqlcmd。

再次注意,你必须用你在运行Docker镜像时使用的密码来改变下面的INSTANCE_PASSWORD 占位符。

/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P "INSTANCE_PASSWORD"

要开始创建数据库,输入下面的SQL查询,创建一个名为goConsole 的数据库。

CREATE DATABASE goConsole

接下来,运行下面的批处理分离器命令来执行你上面输入的SQL查询。

GO 

为了进一步确认数据库的创建,我们可以键入下面的SELECT 语句,按其名称检索所有可用的数据库。

SELECT Name from sys.Databases

然后,运行下面的命令来执行上面的SELECT 语句。

GO

然后,在你的新SQL Server实例内创建的五个数据库将被打印出来。

SQL Server Databases Retrieved Name

创建Reminders

为了在goConsole 数据库中为我们的提醒应用程序创建一个Reminders 表,输入下面两个SQL查询,首先用USE语句将当前数据库从master 设置为goConsole

USE goConsole

接下来,输入下面的CREATE TABLE statement ,在goConsole 数据库中创建一个名为Reminders 的表。

CREATE TABLE Reminders ( ID int IDENTITY(1, 1), title varchar(75), description varchar(175), alias varchar(70)) 

当上面的SQL查询执行时,将创建Reminders 表,该表将包含ID,title,description, 和alias 列。

ID 列使用IDENTITY 函数,确保每次有新记录插入Reminders 表时,ID 列的值会自动增加1

继续运行下面的Go命令,执行我们上面输入的两个SQL查询。

GO  

现在,数据库已经创建,让我们进入本教程的Golang方面。

建立一个Golang控制台应用程序

在本节中,我们将建立一个简化的控制台应用程序,以使用我们刚刚创建的goConsole 数据库。然而,在我们继续前进之前,我们必须了解Golang的sql包和方法。

Golang的sql包来自Golang的标准库,它提供了一个围绕SQL数据库的接口。要使用一个SQL数据库,我们必须使用sql包和SQL数据库的驱动。

当我们将go-mssqldb 数据库驱动与sql包一起使用时,我们将看到这是如何做到的。

Golang方法类似于函数,但是,方法有一个接收参数附加在上面。

在本教程中,我们将创建一个结构类型,包含一个存储指向sql包的数据库处理程序的字段,并创建具有该结构类型接收器的方法。

还要注意的是,我们将只实现提醒数据的CREATE,RETRIEVE, 和DELETE 操作,没有提醒。

创建Go应用程序

要开始创建Go应用程序,请执行下面的两条命令,创建一个新的目录来存储Go项目,并移到mssql-go-cli

# create a new directory
mkdir mssql-go-cli

# move into new directory
cd mssql-go-cli

接下来,执行下面的Go命令,用go.mod 文件启动一个Go项目,以管理该项目中的依赖关系。

go mod init mssql-go-cli

执行下面的go get 命令,安装go-mssqldb 包,以便从这个Go程序中连接你的微软SQL数据库。

go get github.com/denisenkom/go-mssqldb github.com/joho/godotenv/cmd/godotenv

最后,创建一个main.go 文件,将下面的代码块内容添加到该文件中。主函数内的代码作为应用程序的模板,而我们在下一节实现真正的逻辑。

// main.go
package main
import "fmt"

func main() {
  fmt.Println("-> Welcome to the Reminders Console App built using Golang and Microsoft SQL Server")  
}

当应用程序通过go run main.go 命令运行时,一个字符串会打印到控制台,如下图所示。

Welcome Message From The Reminder Console Application In The Terminal

在这一点上,我们的Go应用程序在没有连接到SQL服务器的情况下也能工作。所以,接下来让我们创建一个自定义的数据库包,其中包含建立与SQL Server实例连接的代码。

建立数据库包

Go包是一个目录中包含Go代码的文件集合。因此,为了给这个应用程序创建数据库包,我们必须在mssql-go-cli 项目目录下创建一个新的目录。

为此,创建一个名为database 的目录,并在新目录中创建一个名为database.go 的文件。

接下来,将下面的代码块内容添加到database.go 文件中,以创建一个导出的Database 结构和一个导出的SqlDb 领域。该字段的数据类型指向DB 结构。

// ./database/database.go
package database

import (
   "context"
   "database/sql"
)

type Database struct {
   SqlDb *sql.DB
}

var dbContext = context.Background()

然后,从数据库包中导出的结构将在下一步的主函数中初始化。

构建主函数

让我们继续重构main.go 文件,以提示用户进行操作并接受来自控制台的输入。

将下面的代码块的内容添加到main.go 文件中。

// ./main.go
package main

import (
   "bufio"
   "database/sql"
   "fmt"
   _ "github.com/denisenkom/go-mssqldb"
   "github.com/joho/godotenv"
   "mssql-go-cli/database"
   "os"
)

func main() {
    envErr := godotenv.Load(); if envErr != nil {
       fmt.Printf("Error loading credentials: %v", envErr)
    }

var (
   password = os.Getenv("MSSQL_DB_PASSWORD")
   user = os.Getenv("MSSQL_DB_USER")
   port = os.Getenv("MSSQL_DB_PORT")
   database = os.Getenv("MSSQL_DB_DATABASE")
)

connectionString := fmt.Sprintf("user id=%s;password=%s;port=%s;database=%s", user, password, port, database)

   sqlObj, connectionError := sql.Open("mssql", database.ConnectionString); if connectionError != nil {
      fmt.Println(fmt.Errorf("error opening database: %v", connectionError))
   }

   data := database.Database{
      SqlDb: sqlObj,
   }

   fmt.Println("-> Welcome to Reminders Console App, built using Golang and Microsoft SQL Server")
   fmt.Println("-> Select a numeric option; \n [1] Create a new Reminder \n [2] Get a reminder \n [3] Delete a reminder")

   consoleReader := bufio.NewScanner(os.Stdin)
   consoleReader.Scan()
   userChoice := consoleReader.Text()

   switch userChoice {
   case "1":
      var (
         titleInput,
         descriptionInput,
         aliasInput string
      )
      fmt.Println("You are about to create a new reminder. Please provide the following details:")

      fmt.Println("-> What is the title of your reminder?")
      consoleReader.Scan()
      titleInput = consoleReader.Text()

      fmt.Println("-> What is the description of your reminder?")
      consoleReader.Scan()
      descriptionInput = consoleReader.Text()

      fmt.Println("-> What is an alias of your reminder? [ An alias will be used to retrieve your reminder ]")
      consoleReader.Scan()
      aliasInput = consoleReader.Text()

      data.CreateReminder(titleInput, descriptionInput, aliasInput)

   case "2":
      fmt.Println("-> Please provide an alias for your reminder:")
      consoleReader.Scan()
      aliasInput := consoleReader.Text()

      data.RetrieveReminder(aliasInput)

   case "3":
      fmt.Println("-> Please provide the alias for the reminder you want to delete:")
      consoleReader.Scan()
      deleteAlias := consoleReader.Text()

      data.DeleteReminder(deleteAlias)

   default:
      fmt.Printf("-> Option: %v is not a valid numeric option. Try 1 , 2 , 3", userChoice)
   }
}

一目了然,当上述主函数执行时,文本会打印到控制台,告知用户可用的操作,即创建、检索或删除一个提醒。然后,用户的输入将与switch表达式中的一个案例相匹配。

为了更好地理解这段代码,让我们把文件拆开,逐步浏览主要部分。

了解主函数的代码

首先,.env 文件中的环境变量通过dotenv 包中的Load 函数加载到应用程序中。然后,我们通过调用sql.Open() 打开数据库,并将结果存储在Database 结构中的SqlDb 字段中。

从代码块的第36行开始,一条信息打印到控制台,告知用户创建、检索或删除提醒的操作。来自bufio包的扫描器随后从控制台读取输入信息,并将文本值存储在userChoice 变量中。

然后我们可以使用userChoice 变量作为代码中包含的开关语句的条件表达式。

每当开关语句中的任何一种情况匹配时,就会进一步提示用户通过控制台提供更多的细节,这些细节作为参数传递给一个辅助函数,该函数对数据库执行SQL查询。

例如,第一个案例与1 控制台输入的创建一个提醒相匹配。匹配后,会进一步提示用户为即将创建的提醒提供标题、描述和别名等详细信息。

上面声明的变量存储了所提供的细节,并作为参数传入createReminder ,在连接的微软SQL数据库上执行CREATE 操作。

在这个时刻,我们现在有一个通过控制台接受用户输入的应用程序。然而,这些输入还没有被存储在goConsole 数据库中,因为开关案例中的方法还没有被创建。

让我们继续在数据库包内的一个单独文件中创建createReminder,retrieveReminder, 和deleteReminder 方法。

插入一条提醒记录

要开始建立第一个插入提醒记录的函数,请在数据库目录下创建一个名为operations.go 的文件,并在operations.go 文件中添加以下代码。

// ./database/operations.go
package database
import (
   "database/sql"
   "fmt"
)

func (db Database) CreateReminder(titleInput, aliasInput, descriptionInput string) (int64,  error) {
   var err error

   err = db.SqlDb.PingContext(dbContext); if err != nil {
      return -1, err
   }

   queryStatement :=  `
    INSERT INTO reminders(title, description, alias ) VALUES (@Title, @Description, @Alias);
    select isNull(SCOPE_IDENTITY(), -1);
   `

   query, err := db.SqlDb.Prepare(queryStatement); if err != nil {
      return -1, err
   }

   defer query.Close()

   newRecord := query.QueryRowContext(dbContext,
      sql.Named("Title", titleInput),
      sql.Named("Description", descriptionInput),
      sql.Named("Alias", aliasInput),
   )

   var newID int64
   err = newRecord.Scan(&newID); if err != nil {
      return -1, err
   }

   return newID, nil
}

上面代码块中导出的CreateReminder 方法接受提醒记录的字符串细节。该函数做的第一件事是调用PingContext() 方法来验证连接是否有效。

接下来,一个使用Prepare() 方法准备的SQL语句被存储在queryStatement 变量中。然后,通过将dbContext 和查询参数传入QueryRowContext 方法来执行该SQL语句。

请注意该方法的参数是如何通过NamedArg() 方法使用参数添加到SQL语句中的,而不是直接将参数格式化到SQL语句中。

这个过程减少了SQL注入攻击的风险。然而,你可以在以后采用一个数据验证库。

检索一个提醒记录

要检索一条提醒记录,首先在operations.go 文件中现有方法下面的代码块内添加导出的RetrieveReminder 方法。

RetrieveReminder 方法首先检查数据库连接是否存活,然后执行SQL查询,从数据库中获取一条提醒记录,并将数据打印到控制台。

// ./database/operations.go

func (db Database) RetrieveReminder() error {
err := db.SqlDb.PingContext(dbContext); if err != nil {
   return err
}

   sqlStatement := fmt.Sprintf("SELECT title, description, alias FROM REMINDERS;")

   data, queryErr := db.SqlDb.QueryContext(dbContext, sqlStatement); if queryErr != nil {
      return queryErr
   }

   for data.Next() {
      var title, description, alias string

      nErr := data.Scan(&title, &description, &alias); if nErr != nil {
         return nErr
      }

      fmt.Printf("--> Your Reminder: \n \t Title: %v \n \t Description: %v \n \t Alias: %v \n",
      title, description, alias,
         )

      return nil
   }

   return nil
}

上面,使用QueryContext() 方法,执行一个SELECT SQL语句,从提醒表中读取title,description, 和alias 的值。

然后,QueryContext() 方法在一个结果集中返回选定的表行,我们可以在for 循环中进一步迭代。

for 循环中的Scan() 方法进一步将被迭代的列的值复制到title,description, 和alias 变量中;之后,它们被格式化为一个字符串并打印到控制台。

删除一条提醒记录

要删除一条提醒记录,请将下面的deleteReminder 方法添加到operations.go 文件中现有的CreateReminderRetrieveReminder 方法中。

// ./database/operations.go

func (db Database) DeleteReminder(alias string) error {
var err error

err = db.SqlDb.PingContext(dbContext); if err != nil {
   fmt.Printf("Error checking db connection: %v", err)
}

queryStatement := `DELETE FROM reminders WHERE alias = @alias;`

_, err = db.SqlDb.ExecContext(dbContext, queryStatement, sql.Named("alias", alias))
if err != nil {
   return err
}

fmt.Printf("Reminder with %v alias deleted", alias)

return nil
}

上面的DeleteReminder 方法在其签名中接受了一个字符串别名值。DELETE SQL查询语句使用一个WHERE 子句来指定要删除的提醒记录。

然后查询语句进行准备,函数的alias 参数被用作SQL语句中WHERE 子句的条件。

对于这种情况,ExecContext() 方法执行DELETE SQL语句,因为我们不执行任何行来返回查询。

测试Golang控制台应用程序

goConsole 从上三节中,我们实现了三种方法,从连接的CREATEREADDELETE 数据库中获取记录。现在让我们继续测试整个控制台应用程序。

下面的步骤引导我们使用控制台应用程序的可用选项完成测试过程。

首先,执行下面的命令来运行应用程序;在控制台中输入1 ,在打印到控制台的信息中选择第一个项目,以创建一个提醒。

该应用程序进一步提示我们为即将创建的提醒提供titledescriptionalias

go run ./main.go

再次,执行下面的命令来运行该应用程序。这一次,在控制台中输入2 ,选择检索已保存的提醒信息的第二个选项。

go run ./main.go

Selecting The Second Option In The Terminal When Retrieving Saved Reminders

执行下面的命令,运行应用程序进行最后的测试。这一次,在控制台中键入3 ,选择最后一个选项,删除已保存的提醒信息。应用程序进一步提示我们指定我们要删除的提醒的别名。

go run ./main.go

Terminal Asks User To Specify Which Alias To Delete

在这一点上,我们可以得出结论,我们建立了一个简化的控制台应用程序,使用SQL Server数据库来处理数据。

进一步的考虑

在这篇文章中,我们重点讨论了使用SQL Server数据库的方面。如果你想利用这里写的代码来构建一个更复杂的应用程序,可以考虑以下几点。

使用一个验证库

确保你使用一个数据验证库,在你的SQL查询中使用用户输入之前对其进行验证。这可以减少对连接数据库的SQL注入攻击的风险。

Golang的数据验证库的例子包括go-ozzogovalidator

使用CLI库

其次,使用一个库来创建CLI应用程序。为了避免使教程复杂化,我们依靠开关语句来处理应用流程。然而,你可以通过使用Golang的CLI库(如Cobra)来改进流程。

总结

在本教程中,我们建立了一个控制台应用程序,使用Golang的本地数据库包和go-mssqldb 包来连接SQL Server数据库并对其进行CRUD操作。

本教程中构建的控制台应用程序的代码可以在GitHub的公共仓库中找到。请随意克隆该仓库,并将该应用作为你自己的Golang应用的起点。

The postUsing an SQL database in Golangappeared first onLogRocket Blog.