查看: 88|回复: 0

go简单入门--day4: 连接数据库

[复制链接]

2

主题

13

帖子

17

积分

新手上路

Rank: 1

积分
17
发表于 2023-4-19 18:21:19 | 显示全部楼层 |阅读模式
前言

封面是用自己搭建的ai绘图软件绘制的,以后再也不用去找图了,嘿嘿。
go的基础学习鸽了很久,因为vue源码分析是需要持续性的,不然就给忘了前面分析的啥了。目前那块告一段落了,但是又遇到了ai的降维打击带来的焦虑和无力,导致好几天无所事事,也不知道做什么。。
现在虽然也是无力和焦虑,但是总比等死好。
之前我们知道了如何创建一个module和本地引用它
今天我们来学习下如何接入数据库
<hr/>更多

可以看这篇Accessing relational databases - The Go Programming Language (google.cn)
<hr/>database/sql[1]

首先,这个是一个包,它的作用如下:



img_database/sql

简单地说就是给sql(sql-like)数据库提供泛型接口。
我们接下来会创建一个数据库,然后写代码去访问这个数据库。
这个过程中会使用到这个包。
<hr/>提前准备

windows系统安装MySQL

既然要创建数据库,那么就得有数据库管理系统(database management system (DBMS))。
那么选择哪款呢?官方推荐的MySQL[2] ,相信大家也都不陌生,这里就不矫情去自己找个别的数据库管理系统了。
咱这里直接选MySQL 8.0[3]版本的,它需要的window开发者工具是Microsoft Visual C++2019[4] ,相信大家应该都安装过了,毕竟前面rustup也需要它。
但是我突然发现我安装的是2022的。。。。。
跟着一路往下安装即可。
不过既然追求刺激,那干脆直接放到云服务器上去算了。
<hr/>Linux + Docker安装MySQL

至于啥是Docker[5],简单地说就像是一个沙箱,提供一个独立的环境,类似虚拟机。我个人也是在入门学习阶段,所以这里就不多说了。
Docker上有MySQL的镜像:mysql - Official Image | Docker Hub
咱直接下载安装即可
docker pull mysql:latest


img_docker_mysql_image_download_success

那么镜像就下载完了,可以docker images来查看本地是否已经有这个镜像了



img_mysql_image

不过在这之前,我们先到/usr/local里面创建一个mysql的文件夹,它是用来挂载的,这样mysql的数据就会持久化了,即使是docker重启了也没问题。
cd /usr/local
mkdir -p docker docker/mysql
cd docker/mysql
mkdir -p data conf.d然后我们来创建容器
docker run --name mysql -p 3306:3306 -v /usr/local/docker/mysql/data:/var/lib/mysql -v /usr/local/docker/mysql/conf.d:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=123456 -d mysql

  • --name表示容器的名字
  • -p则是linux映射端口
  • -v:docker容器文件挂载目标,语法是 -v ${目标文件路径}:${容器文件路径} 这一步就是数据持久化的关键
  • -e:配置
  • -d:后台运行
  • mysql:镜像
创建完毕之后可以通过docker ps -a来看下我们的容器



img_all_containers

现在已经安装完毕了,然后我们来连接下试试,不过在这之前,我们还需要进入容器中才行。
docker exec -it [容器id] /bin/bash接着就可以连接mysql了



img_connect_to_mysql

然后我们还需要配置下相关的权限,这样我们远程连接的时候就不会有权限报错的问题。
mysql -u root -p
use mysql;
update user set password_expired = "Y"where user="root";
ALTER USER 'root'@'%'IDENTIFIED WITH mysql_native_password BY '123456';
注意:别忘了关闭防火墙或者在防火墙里面新增3306这个端口的白名单,不然外网访问会被拒绝。
然后本机随便随便用个工具连接试下,我用的HeidiSQL[6],正常连上。



img_connect_to_remote_db

HeidiSQL虽然没有流行的Navicat[7]那么多功能,但是它小巧,免费,一般使用足够了。
不过仅支持window,所以如果你是其它平台用户,那么还是推荐你用Navicat,或者MySQL自带的Workbench
当然,如果不想安装软件,咱直接vscode插件走起:ppz-pro/ppz.vscode: GUI for RDBMS(Relational Database Management System) (github.com)
一老哥的vscode插件,支持多种关系型数据库。

扯得有些远了,那么基本准备完毕,我们开始学习今天的内容。
<hr/>正文

我们来搞个demo,用来存储vintage jazz records也就是古典爵士记录。
我们的内容可以分割为以下几个部分:

  • 初始化项目
  • 创建一个数据库
  • 引入数据库driver
  • 连接上数据库
  • Query也就是查
  • Add即新增数据
初始化项目

随便起个文件夹,然后cd到里面去init。
mkdir data-access
cd data-access
go mod init example/data-access<hr/>创建数据库

我这里就直接用工具创建了



img_create_database

当然,数据插入还是用的命令行,因为一个一个新增非常麻烦。
如果是终端用户,按以下操作
mysql -u root -p
create database recordings;
use recordings;
DROP TABLE IF EXISTS album;
CREATE TABLE album (
  id         INT AUTO_INCREMENT NOT NULL,
  title      VARCHAR(128) NOT NULL,
  artist     VARCHAR(255) NOT NULL,
  price      DECIMAL(5,2) NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO album
  (title, artist, price)
VALUES
  ('Blue Train', 'John Coltrane', 56.99),
  ('Giant Steps', 'John Coltrane', 63.99),
  ('Jeru', 'Gerry Mulligan', 17.99),
  ('Sarah Vaughan', 'Sarah Vaughan', 34.98);

select * from album;这些相信大家都看得懂,就是先创建数据库,然后如果存在对应的album的表,先删除,然后再创建album表,它有四个字段id、title、artist、price,其中id为主键,自增。price的类型是DECIMAL,这个是为了保护精度,毕竟价格这东西很敏感。
然后我们往表里插入四行数据。
最后搜索所有内容展示出来。



img_select_all_from_album

那么建库建表和插入数据就完成了
<hr/>import数据库Driver

然后我们准备开始将数据库接入到我们的go代码里。
这里我们就需要使用到前面提到的database/sql。
不过在这之前,我们还需要一个类似中间件的工具来转换我们的代码变成数据库认识的指令,我们一般管这叫做Driver也就是驱动器。
我们用的是MySQL,所以我们用这个: go-sql-driver/mysql: Go MySQL Driver is a MySQL driver for Go's (golang) database/sql package (github.com)
如果你用的别的数据库,或者你想换一个其它驱动器,你可以在这里面找:SQLDrivers · golang/go Wiki (github.com)
然后回到我们的项目中,我们新建main.go文件
package main

import "github.com/go-sql-driver/mysql"
然后终端运行go get .将它引入。
<hr/>连接数据库

现在我们可以开始连接数据库了
package main

import (
        "database/sql"
        "fmt"
        "log"
        "os"

        "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func main() {
    // Capture connection properties.
    cfg := mysql.Config{
        User:   os.Getenv("DBUSER"),
        Passwd: os.Getenv("DBPASS"),
        Net:    "tcp",
        Addr:   "127.0.0.1:3306",
        DBName: "recordings",
    }
    // Get a database handle.
    var err error
    db, err = sql.Open("mysql", cfg.FormatDSN())
    if err != nil {
        log.Fatal(err)
    }

    pingErr := db.Ping()
    if pingErr != nil {
        log.Fatal(pingErr)
    }
    fmt.Println("Connected!")
}
简单的说下这里面代码做了什么:

  • 定义了一个类型为*sql.DB的变量db,它就是我们的数据处理器,后面我们的指令都是通过它来执行的。不过需要注意,这里我们把db放到全局了,这是为了方便,但是尽量避免这么做,道理大家都懂,或者命名奇形怪状一点即可。
  • 使用driver创建一份config,FormatDSN就是将config装换成可以被数据库认识的strings。
  • sql.open建立连接并且初始化db变量
  • 判断是否有err,如果有则中断程序并且打印错误(不够优雅,以后会遇到优雅的方案)
  • db.Ping:这个是用来确认是否成功和数据库建立联系。在runtime阶段,有些driver会存在延迟,无法立即建立联系,那么这个时候就需要轮询处理,确认是否成功建立了联系。
  • 如果ping失败,直接中断,成功则打印Connected。
不过在我们运行代码之前,我们需要设置两个操作系统环境变量,就是前面的DBUSER和DBPASS
Linux/MacOs
export DBUSER=username // 替换成你数据库对应的用户名
export DBPASS=password // 替换成你数据库的密码
对于window用户
set DBUSER=username
set DBPASS=password我们只能说这里还有一个powerShell需要伺候
$Env:DBUSER = "root"
$Env:DBPASS = "123456"现在我们可以运行代码了
go run .
果不其然有问题



img_error_need_native_password

这个问题是因为MySQL8.0是使用caching_sha2_password来加密用户密码的,我们可以把它改为低版本的caching_sha2_password加密方案。
进入到我们之前挂载的文件夹/usr/local/docker/mysql/conf.d里面创建一个my.cnf作为mysql的配置文件。
然后新增如下内容
[mysql]
#设置mysql客户端默认字符集
default-character-set=UTF8MB4
[mysqld]
#设置3306端口
port=3306
#允许最大连接数
max_connections=200
#允许连接失败的次数
max_connect_errors=10
#默认使用“mysql_native_password”插件认证
default_authentication_plugin=mysql_native_password
#服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=UTF8MB4
#开启查询缓存
explicit_defaults_for_timestamp=true
#创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
#等待超时时间秒
wait_timeout=60
#交互式连接超时时间秒
interactive-timeout=600这里最重要的其实是default_authentication_plugin=mysql_native_password这一行,将默认的加密方式改为mysql_native_password。
然后重启docker里的mysql
docker restart [容器id] 重启完之后再回到我们本机项目中运行go run .
然后你就会遇到另一个问题



img_can_not_requested_auth_plugin_mysql_native_password

这个是因为driver默认没有开启这个plugin,我们需要将对应的字段AllowNativePasswords指为true
        cfg := mysql.Config{
                User:                 os.Getenv("DBUSER"),
                Passwd:               os.Getenv("DBPASS"),
                Net:                  "tcp",
                Addr:                 "127.0.0.1:3306",
                DBName:               "recordings",
                AllowNativePasswords: true,
        }
然后再重新运行下go run .



img_connect_mysql_success

这样就连接成功了。
<hr/>

在开始查之前,我们先来定义下行的类型
type Album struct {
    ID     int64
    Title  string
    Artist string
    Price  float32
}
然后我们来写查询的逻辑
// albumsByArtist queries for albums that have the specified artist name.
func albumsByArtist(name string) ([]Album, error) {
    // An albums slice to hold data from returned rows.
    var albums []Album

    rows, err := db.Query("SELECT * FROM album WHERE artist = ?", name)
    if err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    defer rows.Close()
    // Loop through rows, using Scan to assign column data to struct fields.
    for rows.Next() {
        var alb Album
        if err := rows.Scan(&alb.ID, &alb.Title, &alb.Artist, &alb.Price); err != nil {
            return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
        }
        albums = append(albums, alb)
    }
    if err := rows.Err(); err != nil {
        return nil, fmt.Errorf("albumsByArtist %q: %v", name, err)
    }
    return albums, nil
}
简单的说下这里面做了什么:

  • 我们使用了db.Query方法传入了一个SQL字符串语句。选择所有artist为传入的name的数据。
  • 断开数据的连接,不然任何的操作都会直接影响到原数据。
  • 遍历这一组数据,将有效的数据放到albums里。rows.Scan方法用于将数据里对应的字段的值赋值给传入的指针指向的数据。&这个大家应该都不陌生,就是引用的意思,在这里可以直接看做指针。当行数据赋值给alb变量之后,再将它append到albums这个slice即切片里。
  • 最后返回数据,如果遇到问题,返回nil和数据。经典:if err != nil return nil, err。。。
然后我们来用下这个函数,回到main函数中。
// ...
albums, err := albumsByArtist("John Coltrane")
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Albums found: %v\n", albums)



img_found_row_where_artist_=_John_Coltrane

当然,这里是非常非常基础的使用,实际上你应该要把SQL语句以动态的方式传入。
<hr/>

直接上代码
// addAlbum adds the specified album to the database,
// returning the album ID of the new entry
func addAlbum(alb Album) (int64, error) {
    result, err := db.Exec("INSERT INTO album (title, artist, price) VALUES (?, ?, ?)", alb.Title, alb.Artist, alb.Price)
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    id, err := result.LastInsertId()
    if err != nil {
        return 0, fmt.Errorf("addAlbum: %v", err)
    }
    return id, nil
}
这个就没啥好说的了,不过这里用的是db.Exec而不是Query的,需要注意。
调用LastInsertId方法获取最后一条数据的id并返回。
我们在main函数中引用下
albID, err := addAlbum(Album{
    Title:  "The Modern Sound of Betty Carter",
    Artist: "Betty Carter",
    Price:  49.99,
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("ID of added album: %v\n", albID)



img_insert_album

额,我这之前调整过数据,所以id没有连续
<hr/>总结

大部分时间都是在配置环境和排错。。
代码还是挺好理解的,虽然分析的很少。
另外相信大家也都看到了各处的err处理场景。。怎么说呢,一言难尽。。
大时代的变化,不去拥抱变化,最终只会被淘汰,这就是我这段时间一直在焦虑痛苦的地方,但是无力啊。。。
ai这趟列车是处于到来前的轰隆声,还是已经在经过准备把我远远甩去了呢?
参考


  • ^database/sql https://pkg.go.dev/database/sql
  • ^MySQL https://dev.mysql.com/doc/mysql-installation-excerpt/5.7/en/
  • ^MySQL8.0-for-window https://dev.mysql.com/downloads/file/?id=516927
  • ^vs 2019 https://www.microsoft.com/en-us/download/developer-tools.aspx
  • ^Docker https://docs.docker.com/get-docker/
  • ^download HeiDiSQL https://www.heidisql.com/download.php
  • ^Navicat https://www.navicat.com/en/download/navicat-for-mysql
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表