<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 數(shù)據(jù)存儲篇(五):建立數(shù)據(jù)庫連接并進行增刪改查操作

          共 6752字,需瀏覽 14分鐘

           ·

          2020-10-03 12:22

          前面給大家介紹了 Go 語言中的內(nèi)存存儲和文件存儲,文件存儲的好處是可以持久化數(shù)據(jù),但是并不是 Web 應(yīng)用數(shù)據(jù)存儲的終極方案,因為這樣存儲起來的數(shù)據(jù)檢索和管理起來非常麻煩,為此又誕生了數(shù)據(jù)庫管理系統(tǒng)來處理數(shù)據(jù)的增刪改查。數(shù)據(jù)庫又可以劃分為關(guān)系型數(shù)據(jù)庫(RDBMS)和非關(guān)系型數(shù)據(jù)庫(NoSQL),前者比如 MySQL、Oracle,后者比如 Redis、MongoDB,這里我們以當前最流行的開源關(guān)系型數(shù)據(jù)庫 MySQL 為例進行介紹。

          1、初始化數(shù)據(jù)庫

          開始之前,我們先要連接到 MySQL 服務(wù)器初始化數(shù)據(jù)庫和數(shù)據(jù)表。

          注:如果你還沒有在本地安裝 MySQL 數(shù)據(jù)庫,需要先進行安裝,使用 Docker 啟動或者去 MySQL 官網(wǎng)下載安裝包安裝均可,Mac 系統(tǒng)中還可以使用 Homebrew 進行安裝,然后選擇一個自己喜歡的 GUI 客戶端,學院君本地使用的是 TablePlus。

          做好上述準備工作連接到 MySQL 服務(wù)端之后,就可以創(chuàng)建一個名為 test_db 的數(shù)據(jù)庫:

          然后在這個數(shù)據(jù)庫中創(chuàng)建一張名為 posts 的測試數(shù)據(jù)表用來存儲文章信息:

          CREATE?TABLE?`posts`?(
          ??`id`?bigint?unsigned?AUTO_INCREMENT,
          ??`title`?varchar(100)?DEFAULT?NULL,
          ??`content`?text,
          ??`author`?varchar(30)?DEFAULT?NULL,
          ??PRIMARY?KEY?(`id`),
          ??UNIQUE?KEY?`id`?(`id`)
          )?ENGINE=InnoDB?DEFAULT?CHARSET=utf8mb4?COLLATE=utf8mb4_unicode_ci;

          2、建立數(shù)據(jù)庫連接

          接下來,我們就可以在 Go 程序中編寫代碼建立與數(shù)據(jù)庫的連接,然后對 posts 表進行增刪改查操作了。

          Go 語言并沒有提供 MySQL 客戶端擴展包的官方實現(xiàn),只是提供了一個抽象的 database/sql 接口,只要第三方數(shù)據(jù)庫客戶端實現(xiàn)該接口聲明的方法,用戶就可以在不同的第三方數(shù)據(jù)庫客戶端擴展包實現(xiàn)之間進行切換,而不需要調(diào)整任何業(yè)務(wù)代碼。

          實現(xiàn) database/sql 接口的 MySQL 第三方擴展包很多,比較流行的有 go-sql-driver/mysql 和 ORM 擴展包 go-gorm/gorm,我們先來看看如何通過 go-sql-driver/mysql 在 Go 程序中與 MySQL 數(shù)據(jù)庫交互。

          我們可以在測試代碼 db.go 中編寫一段 init 方法,在每次代碼執(zhí)行 main 入口函數(shù)之前先建立數(shù)據(jù)庫連接:

          import?(
          ????"database/sql"
          ????_?"github.com/go-sql-driver/mysql"
          )

          var?Db?*sql.DB

          func?init()??{
          ????var?err?error
          ????Db,?err?=?sql.Open("mysql",?"root:root@/test_db?charset=utf8mb4&parseTime=true")
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}
          }

          sql.DB 是一個用于操作數(shù)據(jù)庫的結(jié)構(gòu)體,維護的是一個數(shù)據(jù)庫連接池。數(shù)據(jù)庫連接通過 sql.Open 方法設(shè)置,該方法接收一個數(shù)據(jù)庫驅(qū)動(這里是 mysql)和數(shù)據(jù)源名稱字符串(按照位置填充即可,更多細節(jié)請參考該數(shù)據(jù)庫包的官方文檔):

          [username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN]

          注:如果 MySQL 服務(wù)器運行在本地,則 address 字段(IP + 端口號)留空。

          成功后返回一個 sql.DB 指針,然后你就可以拿著這個指針操作數(shù)據(jù)庫了。

          需要注意的是 Open 方法并沒有真正建立連接,也不會對傳入的參數(shù)做任何驗證,它只是負責初始化 sql.DB 結(jié)構(gòu)體字段而已,數(shù)據(jù)庫連接只有在后面真正需要的時候才會建立,是一個懶加載的過程。這樣做的好處是提升應(yīng)用性能,避免不必要的數(shù)據(jù)庫連接開銷。

          另外,sql.DB 也不需要關(guān)閉,sql.DB 維護的是一個連接池,在我們的示例代碼中定義了一個全局的 Db 變量來指向它,你還可以在創(chuàng)建 sql.DB 后將其傳遞給要操作數(shù)據(jù)庫的方法。

          接下來,我們來看下 Open 方法的參數(shù),第一個參數(shù)是數(shù)據(jù)庫驅(qū)動,要支持這個驅(qū)動需要調(diào)用 sql.Register 方法進行注冊,由于我們使用了 go-sql-driver/mysql 這個第三方包,這一步是在 mysql 包的 init 方法中完成的(driver.go):

          func?init()?{
          ????sql.Register("mysql",?&MySQLDriver{})
          }

          后面的驅(qū)動結(jié)構(gòu)體需要實現(xiàn) sql 包的 driver.Driver 接口。

          當我們通過下面這段代碼引入 mysql 包時:

          _?"github.com/go-sql-driver/mysql"

          就會調(diào)用 mysql 包的 init 方法。

          Go 官方?jīng)]有提供任何數(shù)據(jù)庫驅(qū)動包,只是在 sql.Driver 中聲明了接口,第三方驅(qū)動包只要實現(xiàn)這些接口就好了。另外,我們在導入第三方包的時候,需要在前面加上短劃線 _,這樣做的好處是不會直接使用第三方包中的方法,只能使用官方 database/sql 中提供的方法,當?shù)谌桨壔蛘咝枰{(diào)整數(shù)據(jù)庫驅(qū)動時,不需要修改應(yīng)用中的代碼。

          注:如果你對這一塊接口與實現(xiàn)的細節(jié)不清楚,可以回顧 Go 入門教程中的面向?qū)ο缶幊滩糠?/a>。

          3、增刪改查示例代碼

          數(shù)據(jù)庫初始化完成并設(shè)置好連接配置之后,就可以在 Go 應(yīng)用中與數(shù)據(jù)庫進行交互了。我們將編寫一段對文章表進行增刪改查的示例代碼來演示 Go 語言中的數(shù)據(jù)庫操作。

          注:以下所有示例代碼都是在 db.go 中編寫。

          定義 Post 結(jié)構(gòu)體

          首先我們需要定義一個表示文章表數(shù)據(jù)結(jié)構(gòu)的結(jié)構(gòu)體:

          type?Post?struct?{
          ????Id?int
          ????Title?string
          ????Content?string
          ????Author?string
          }

          創(chuàng)建新文章

          然后我們編寫在數(shù)據(jù)庫中創(chuàng)建文章記錄的 Create 方法,其實就是在上述全局 Db 數(shù)據(jù)庫連接上執(zhí)行 SQL 插入語句而已,對應(yīng)的示例代碼如下::

          func?(post?*Post)?Create()?(err?error)?{
          ????sql?:=?"insert?into?posts?(title,?content,?author)?values?(?,??,??)"
          ????stmt,?err?:=?Db.Prepare(sql)
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}
          ????defer?stmt.Close()

          ????res,?err?:=?stmt.Exec(post.Title,?post.Content,?post.Author)
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}

          ????postId,?_?:=?res.LastInsertId()
          ????post.Id?=?int(postId)
          ????return
          }

          注:這里我們使用了預處理語句,以避免 SQL 注入攻擊,如果你有 PHP 或者其他語言數(shù)據(jù)庫編程基礎(chǔ)的話,應(yīng)該很容易看懂這些代碼。

          實際上,我們還可以通過 stmt.QueryRow(post.Title, post.Content, post.Author) 來執(zhí)行插入操作,效果是一樣的,也可以直接通過 Db.Exec 執(zhí)行插入操作:

          res,?err?:=?Db.Exec(sql,?post.Title,?post.Content,?post.Author)

          獲取單篇文章

          創(chuàng)建完文章后,可以通過 Db.QueryRow 執(zhí)行一條 SQL 查詢語句查詢單條記錄并將結(jié)果映射到 Post 結(jié)構(gòu)體中。

          func?GetPost(id?int)?(post?Post,?err?error)?{
          ????post?=?Post{}
          ????err?=?Db.QueryRow("select?id,?title,?content,?author?from?posts?where?id?=??",?id).
          ????????Scan(&post.Id,?&post.Title,?&post.Content,?&post.Author)
          ????return
          }

          獲取文章列表

          我們可以使用 sql.DB 提供的 Query 方法來查詢多條文章記錄:

          func?Posts(limit?int)?(posts?[]Post,?err?error)?{
          ????rows,?err?:=?Db.Query("select?id,?title,?content,?author?from?posts?limit??",?limit)
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}
          ????defer?rows.Close()
          ????for?rows.Next()?{
          ????????post?:=?Post{}
          ????????err?=?rows.Scan(&post.Id,?&post.Title,?&post.Content,?&post.Author)
          ????????if?err?!=?nil?{
          ????????????panic(err)
          ????????}
          ????????posts?=?append(posts,?post)
          ????}
          ????return
          }

          該方法返回的是 sql.Rows 接口,它是一個迭代器,你可以通過循環(huán)調(diào)用其 Next 方法返回其中的每個 sql.Row 對象,直到 sql.Rows 中的記錄值為空(此時返回 io.EOF)。

          在循環(huán)體中,我們將每個 sql.Row 對象映射到 Post 對象,再將這個 Post 對象添加到 posts 切片中。

          其實對于單條記錄,也可以使用類似的方式實現(xiàn),畢竟單條記錄查詢是 SELECT 查詢的特例:

          func?GetPost(id?int)?(post?Post,?err?error)?{
          ????rows,?err?:=?Db.Query("select?id,?title,?content,?author?from?posts?where?id?=???limit?1",?id)
          ????if?err?!=?nil?{
          ????????panic(err)
          ????}
          ????defer?rows.Close()
          ????for?rows.Next()?{
          ????????post?=?Post{}
          ????????err?=?rows.Scan(&post.Id,?&post.Title,?&post.Content,?&post.Author)
          ????????if?err?!=?nil?{
          ????????????panic(err)
          ????????}
          ????}
          ????return
          }

          當然,前面的 Db.Query 也可以調(diào)整為預處理語句實現(xiàn),只是更繁瑣一些:

          stmt,?err?:=?Db.Prepare("select?id,?title,?content,?author?from?posts?limit??")
          if?err?!=?nil?{
          ????panic(err)
          }
          defer?stmt.Close()
          rows,?err?:=?stmt.Query(limit)
          if?err?!=?nil?{
          ????panic(err)
          }
          ...?//?后續(xù)其他操作代碼

          更新文章

          對于已存在的文章記錄,可以通過執(zhí)行 SQL 更新語句進行修改:

          func?(post?*Post)?Update()?(err?error)??{
          ????stmt,?err?:=?Db.Prepare("update?posts?set?title?=??,?content?=??,?author?=???where?id?=??")
          ????if?err?!=?nil?{
          ????????return
          ????}
          ????stmt.Exec(post.Title,?post.Content,?post.Author,?post.Id)
          ????return
          }

          當然,也可以通過 stmt.QueryRow 以及 Db.Exec 這兩種方式來處理上述操作,使用 Db.Exec 方法更簡潔:

          func?(post?*Post)?Update()?(err?error)??{
          ????_,?err?=?Db.Exec("update?posts?set?title?=??,?content?=??,?author?=???where?id?=??",
          ????????post.Title,?post.Content,?post.Author,?post.Id)
          ????return
          }

          Db.Exec 方法返回的是 sql.Result 接口,該接口支持以下兩個方法:

          -w632

          我們不需要處理這個 Result 對象,所以通過 _ 將其忽略。

          刪除文章

          刪除操作和更新操作類似,只是將 UPDATE 語句調(diào)整為 DELETE 語句而已:

          func?(post?*Post)?Delete()?(err?error)?{
          ????stmt,?err?:=?Db.Prepare("delete?from?posts?where?id?=??")
          ????if?err?!=?nil?{
          ????????return
          ????}
          ????stmt.Exec(post.Id)
          ????return
          }

          當然上述操作可以通過 stmt.QueryRowDb.Exec 方法來實現(xiàn):

          func?(post?*Post)?Delete()?(err?error)?{
          ????_,?err?=?Db.Exec("delete?from?posts?where?id?=??",?post.Id)
          ????return
          }

          4、整體測試

          最后,我們在 db.go 中編寫入口函數(shù) main 測試一下上述數(shù)據(jù)庫增刪改查操作是否可以正常運行:

          func?main()??{
          ????post?:=?Post{Title:?"Go?語言數(shù)據(jù)庫操作",?Content:?"基于第三方?go-sql-driver/mysql?包實現(xiàn)?MySQL?數(shù)據(jù)庫增刪改查",?Author:?"學院君"}

          ????//?創(chuàng)建記錄
          ????post.Create()
          ????fmt.Println(post)

          ????//?獲取單條記錄
          ????dbPost,?_?:=?GetPost(post.Id)
          ????fmt.Println(dbPost)

          ????//?更新記錄
          ????dbPost.Title?=?"Golang?數(shù)據(jù)庫操作"
          ????dbPost.Update()

          ????//?獲取文章列表
          ????posts,?_?:=?Posts(1)
          ????fmt.Println(posts)

          ????//?刪除記錄
          ????dbPost.Delete()
          }

          注:運行前,記得通過 Go Module 下載 go-sql-driver/mysql 依賴。

          在終端運行 db.go,輸出如下,表示這段數(shù)據(jù)庫增刪改查代碼可以正常運行:

          好了,關(guān)于數(shù)據(jù)庫增刪改查基本操作就簡單介紹到這里,下篇教程,我們來看看如何在 MySQL 數(shù)據(jù)庫中實現(xiàn)不同表之間的關(guān)聯(lián)查詢和更新。

          (全文完)



          推薦閱讀


          福利

          我為大家整理了一份從入門到進階的Go學習資料禮包(下圖只是部分),同時還包含學習建議:入門看什么,進階看什么。

          關(guān)注公眾號 「polarisxu」,回復?ebook?獲?。贿€可以回復「進群」,和數(shù)萬 Gopher 交流學習。



          瀏覽 58
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜福利男女 | 成年人毛片国产网站国产片 | 免费播放欧美一级电影 | 影音先锋啪 | 怡春院在线观看 |