十二要素应用方法论中的进程原则主张,应用程序作为无状态进程执行。这意味着需要持久化的数据应该存储在外部支持服务中,如数据库。
当把这个原则应用到你的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容器。
创建一个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实例内创建的五个数据库将被打印出来。
创建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
命令运行时,一个字符串会打印到控制台,如下图所示。
在这一点上,我们的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
文件中现有的CreateReminder
和RetrieveReminder
方法中。
// ./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
从上三节中,我们实现了三种方法,从连接的CREATE
、READ
、DELETE
数据库中获取记录。现在让我们继续测试整个控制台应用程序。
下面的步骤引导我们使用控制台应用程序的可用选项完成测试过程。
首先,执行下面的命令来运行应用程序;在控制台中输入1
,在打印到控制台的信息中选择第一个项目,以创建一个提醒。
该应用程序进一步提示我们为即将创建的提醒提供title
、description
、alias
。
go run ./main.go
再次,执行下面的命令来运行该应用程序。这一次,在控制台中输入2
,选择检索已保存的提醒信息的第二个选项。
go run ./main.go
执行下面的命令,运行应用程序进行最后的测试。这一次,在控制台中键入3
,选择最后一个选项,删除已保存的提醒信息。应用程序进一步提示我们指定我们要删除的提醒的别名。
go run ./main.go
在这一点上,我们可以得出结论,我们建立了一个简化的控制台应用程序,使用SQL Server数据库来处理数据。
进一步的考虑
在这篇文章中,我们重点讨论了使用SQL Server数据库的方面。如果你想利用这里写的代码来构建一个更复杂的应用程序,可以考虑以下几点。
使用一个验证库
确保你使用一个数据验证库,在你的SQL查询中使用用户输入之前对其进行验证。这可以减少对连接数据库的SQL注入攻击的风险。
Golang的数据验证库的例子包括go-ozzo和govalidator。
使用CLI库
其次,使用一个库来创建CLI应用程序。为了避免使教程复杂化,我们依靠开关语句来处理应用流程。然而,你可以通过使用Golang的CLI库(如Cobra)来改进流程。
总结
在本教程中,我们建立了一个控制台应用程序,使用Golang的本地数据库包和go-mssqldb
包来连接SQL Server数据库并对其进行CRUD操作。
本教程中构建的控制台应用程序的代码可以在GitHub的公共仓库中找到。请随意克隆该仓库,并将该应用作为你自己的Golang应用的起点。
The postUsing an SQL database in Golangappeared first onLogRocket Blog.